⚙️ 第42篇:JVM垃圾回收与JIT编译:finalize、引用计数、GC算法、即时编译器全解析(2026版)
📌 系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第41篇:GET和POST的区别、堆和栈的区别 |
➡️ 下一篇:第43篇:Java字节码,javap命令,解读字节码清单
一、核心知识点
finalize()方法(已弃用):GC 前调用,执行时机不确定,不推荐使用- 引用计数法(JVM 不使用):循环引用问题
- JVM 垃圾回收算法:
- 复制算法(年轻代)
- 标记-清除(老年代)
- 标记-整理(老年代)
- JIT(Just-In-Time)即时编译器:热点代码编译为机器码,提高性能
- 其他相关概念:对象复活、GC Roots、Stop-The-World、JVM 参数调优
二、通俗讲解(1分钟开心学)
1. finalize——已过时的“遗言”
finalize() 是 Object 类的一个方法,垃圾回收器在回收对象前会调用它。你可以覆盖它来做一些清理(比如关闭文件)。但它的执行时机不确定(不知道什么时候被回收),而且可能导致对象“复活”(重新被引用),性能也差。从 Java 9 开始已弃用。推荐使用 try-with-resources 或 Cleaner。
生活类比:
finalize()就像临终遗言,但不确定什么时候念,还可能被人救活(复活),所以不靠谱。现代 Java 直接用try-with-resources自动关闭资源,就像离开房间自动关灯,安全可靠。
2. 引用计数法(JVM 为什么不使用)
每个对象记录被引用的次数,次数为零时回收。简单,但无法处理循环引用:A 引用 B,B 引用 A,外部没有引用它们,但计数为 1,永远不会回收。JVM 使用可达性分析算法(GC Roots)来代替。
3. JVM 垃圾回收算法对比
| 算法 | 原理 | 优点 | 缺点 | 适用区域 |
|---|---|---|---|---|
| 复制算法 | 内存分为两块,只用一块,存活对象复制到另一块,清空原区域 | 无碎片,简单高效 | 内存利用率低(只用一半) | 年轻代(对象存活率低) |
| 标记-清除 | 标记存活对象,清除未标记的 | 无需移动对象 | 产生内存碎片,效率随对象增多下降 | 老年代(CMS 回收器) |
| 标记-整理 | 标记后,将存活对象向一端移动,清理边界外内存 | 无碎片 | 移动对象开销大,需 Stop-The-World | 老年代(Serial Old、Parallel Old) |
4. JIT 即时编译器
Java 程序开始运行时,字节码由解释器执行。JVM 会检测“热点代码”(频繁执行的方法或循环),将其编译成本地机器码(编译后直接执行,不再解释)。这能大幅提升性能,尤其是长期运行的应用。
生活类比:
JIT 就像你每天通勤走同样的路线,走几次后你不再查地图(解释执行),而是凭记忆快速走过去(编译优化)。
finalize就像临终遗言,但不确定什么时候念,还可能被人救活(复活),所以不靠谱。
三、实操代码案例 + 场景说明
finalize 示例(不推荐,仅用于理解):
class Resource {
@Override
protected void finalize() throws Throwable {
System.out.println("资源被回收了");
super.finalize();
}
}
public class FinalizeDemo {
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Resource();
}
System.gc(); // 建议 GC,但不保证立即执行
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
对象复活示例(了解即可,不要这样写):
public class Resurrection {
public static Resurrection instance = null;
@Override
protected void finalize() {
System.out.println("finalize 执行,对象复活");
instance = this;
}
public static void main(String[] args) throws Exception {
instance = new Resurrection();
instance = null;
System.gc();
Thread.sleep(100);
System.out.println(instance); // 不为 null,复活成功
}
}
JIT 效果示意:
public class JITDemo {
public static void main(String[] args) {
long start = System.nanoTime();
int sum = 0;
for (int i = 0; i < 100_000_000; i++) {
sum += i;
}
long end = System.nanoTime();
System.out.println("耗时:" + (end - start) / 1e6 + " ms");
// 多次运行会发现,第二次开始明显变快(JIT 已优化)
}
}
四、避坑要点(高频踩坑汇总)
| 错误/误区 | 后果 | 正确做法 |
|---|---|---|
依赖 finalize() 释放资源 | 资源泄漏,因为不知道何时调用 | 使用 try-with-resources 或 Cleaner |
| 认为引用计数法适合 Java | 无法解决循环引用 | JVM 用可达性分析(GC Roots) |
| 担心 JIT 编译会浪费启动时间 | 过度优化 | JIT 对长期运行的应用非常有益 |
手动调用 System.gc() | 不一定会立即 GC,且影响性能 | 让 JVM 自动管理 |
误以为 finalize() 只会执行一次 | 对象复活后可再次被回收,finalize() 不会重复执行 | 了解即可,实际不要用 |
| 混淆年轻代和老年代的 GC 算法 | 选错回收器导致性能问题 | 年轻代用复制算法,老年代用标记-清除或标记-整理 |
| 认为 JIT 编译所有代码 | 只编译热点代码 | 了解 JIT 触发条件(方法调用次数、循环次数阈值) |
五、面试高频考点(附详细答案)
Q1:为什么 JVM 不用引用计数法?
无法解决循环引用问题(如 A 引用 B,B 引用 A,外部无引用但计数不为 0),且每个对象都需要维护计数器,有额外开销。JVM 使用可达性分析(GC Roots)代替。
Q2:年轻代和老年代各自使用什么 GC 算法?
年轻代:复制算法(Eden + Survivor 空间),因为对象存活率低。老年代:标记-清除(如 CMS)或标记-整理(如 Serial Old、G1 的部分行为),因为对象存活率高,需要减少移动。
Q3:JIT 编译器的作用是什么?
将热点字节码编译成本地机器码,提高执行效率。与 AOT(提前编译)相比,JIT 可根据运行时信息做更激进的优化(如内联、逃逸分析)。
Q4:finalize() 方法有什么问题?
执行时机不确定(依赖 GC),可能导致对象复活,性能差,且容易引发资源泄漏。Java 9 起已标记为弃用。
Q5:什么是 Stop-The-World?哪些情况会发生?
Stop-The-World 是指 JVM 暂停所有应用线程,只让 GC 线程工作。发生在 GC 的某些阶段(如标记、整理、复制),G1、ZGC 等回收器努力减少 STW 时间。
Q6:如何判断一个对象是否可回收?
通过可达性分析:从 GC Roots(如栈帧中的局部变量、静态变量、JNI 引用等)出发,无法到达的对象即不可达,可被回收。
Q7:GC Roots 包括哪些?
包括:虚拟机栈(栈帧中的局部变量)引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中 JNI 引用的对象、活跃线程等。
Q8:JIT 编译的触发条件是什么?
方法调用计数器(默认 10000 次)或循环回边计数器达到阈值,触发 C1(快速编译)或 C2(优化编译)。可通过
-XX:CompileThreshold调整。
六、练习题
- 简答:复制算法的优点和缺点。
- 代码分析:为什么
finalize()可以被调用多次(复活)?如何避免? - 动手:写一个程序,让 JIT 编译一个热点方法,可以通过
-XX:+PrintCompilation观察编译日志。 - 思考:如果老年代也使用复制算法会有什么问题?
- 排查:如何查看 JVM 默认的垃圾回收器?
📊 你的学习进度
- 当前:第42篇 / 共44篇 · 第六阶段:NIO、泛型、JVM内幕、字节码(第36~44篇)
- ✅ 已完成:第1~41篇
- 📖 正在学:第42篇
- ⏳ 待学习:第43~44篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
💡 本专栏每篇都包含:避坑表 + 面试高频考点 + 练习题。每天30分钟,100天拿offer!
👉 下一篇文章预告
《第43篇:Java字节码,javap命令,解读字节码清单》
内容简介:字节码作用、javap命令、常见字节码指令、通过字节码看语法糖。
💡 学完这篇,你将能读懂.class文件,理解JVM执行原理。
📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!
📌 除了《Java 100 天进阶之路 | 从入门到上岗就业》系列文章,我也在深挖智能物流实战(出版社WMS、托盘调度、机器学习落地)。如果你对技术在不同领域的实战感兴趣,欢迎点击我的头像,看看专栏《出版社物流WMS智能调度实战》。技术相通,思路可鉴。
1760

被折叠的 条评论
为什么被折叠?



