JVM垃圾回收与JIT编译:finalize、引用计数、GC算法、即时编译器全解析(2026版)

⚙️ 第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-resourcesCleaner

生活类比
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-resourcesCleaner
认为引用计数法适合 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 调整。

六、练习题

  1. 简答:复制算法的优点和缺点。
  2. 代码分析:为什么 finalize() 可以被调用多次(复活)?如何避免?
  3. 动手:写一个程序,让 JIT 编译一个热点方法,可以通过 -XX:+PrintCompilation 观察编译日志。
  4. 思考:如果老年代也使用复制算法会有什么问题?
  5. 排查:如何查看 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智能调度实战》。技术相通,思路可鉴。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值