Java学习随笔:JVM 如何判断对象可以被回收(2026-09-17)

学习 Java 的过程中,很多同学会把注意力放在语法、集合和框架上,却容易忽略 JVM 内部究竟发生了什么。最近我在复习垃圾回收时,才对“对象什么时候会被回收”这个问题有了更清楚的认识。它并不像表面看起来那么简单:并不是某个变量不再使用,对象就会立刻消失,而是需要虚拟机通过一套相对严谨的机制去判断。

最早接触这个问题时,我脑子里只有一个答案,那就是引用计数。引用计数的思路非常直观:给每个对象维护一个计数器,每当有引用指向它,计数器就加一;引用失效时,计数器就减一;当计数器归零,就说明没有人在使用它,可以被回收。这种算法实现简单、判定也快,但 Java 并没有把它作为主要的判断手段,原因就在于它处理不了循环引用。比如两个对象互相引用,即使外部已经没有任何引用指向它们,它们的计数器也始终不为零,于是这两块内存就成了永远无法被回收的“孤岛”。Python 等语言会借助其他手段补足这个问题,而 Java 则干脆换了一条思路。

Java 主流虚拟机采用的是“可达性分析”算法。这个算法的出发点是先确定一批根对象,也就是 GC Roots。GC Roots 通常包括虚拟机栈中引用的对象、方法区中静态属性引用的对象、常量引用的对象、本地方法栈中 JNI 引用的对象,以及活跃线程和被同步锁持有的对象等。从这些根节点出发,沿着引用关系不断向下搜索,就会形成一条条“引用链”。凡是能够通过引用链到达的对象,就认为它仍然存活;反过来,从 GC Roots 出发怎么也到不了的对象,就失去了存活的意义,会被判定为可回收对象。

第一次看到“可达性分析”时,我觉得它很抽象,后来画了几次图才真正理解:判断一个对象是否存活,本质上不是看它身上还有没有引用,而是看它还能不能被程序“够得着”。哪怕某个对象还挂着几根引用,只要这些引用最终都断掉了根,它依然是不可达的。这也解释了为什么循环引用在可达性分析面前不再是难题:两个对象互相牵着,但它们都够不到 GC Roots,最终还是会一起被判定为可回收。

理解了可达性分析之后,我对虚拟机的分代回收也有了更整体的认识。年轻代里的对象生命周期短,很多对象“朝生暮死”,所以 Minor GC 可以频繁、快速地进行;而老年代里的对象活得久,回收的频率就要低一些。无论是哪一代的回收,第一步都是先做标记,找出哪些对象存活、哪些对象不可达,然后才进入清除、复制或整理等后续步骤。以前我总觉得垃圾回收就是“删掉没用的对象”,现在才明白,它其实是一套“标记、判定、回收”配合起来的完整流程。

这次学习也让我意识到,很多看似基础的知识点,背后都藏着设计者面对具体问题时的取舍。引用计数输在循环引用,可达性分析赢在更符合对象之间真实的引用关系,但它也要付出扫描和停顿等代价。理解这些取舍,比单纯记住结论更有价值。接下来我打算继续顺着这条线,去看看不同的垃圾回收器究竟在这些步骤上做了哪些优化。

posted @ 2026-09-17 22:46  梦幻泡影Qv'Q  阅读(3)  评论(0)    收藏  举报