JVM 如何判断对象是否存活
引用计数法
给对象添加一个引用计数器,每当有一个地方引用它,计数器就会加 1;
当引用失效,计数器就减 1;
任何时候计数器为 0 的对象就是不可能再被使用的。
这个方法简单高效,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
循环引用会导致对象无法被回收,最终会导致内存泄漏及内存溢出
可达性分析法
这个算法的基本思想就是通过一系列的称为 “GC Roots”的对象作为起点,从这些节点开始向下搜索,节点所走过的路劲称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
但是,并不是说当进行完可达性分析算法后,即可证明某对象可以被 GC。对象是否存活,需要两次标记:
第一次标记通过可达性分析算法。如果没有 GC Roots 相连接的引用链,那么将第一次标记;
如果对象的
finalize()
方法被覆盖并且没有执行过,则放在 F-Queue 队列中等待执行(不一定会执行),如果一段时间后该队列的finalize()
方法被执行且和 GC Roots 关联,则移出“即将回收”集合。如果仍然没有关联,则进行第二次标记,才会对该对象进行回收。
不过现在都不提倡finalize
方法,它的本意是像 Cpp 一样在对象销毁前执行,但是它影响了 Java 的安全和 GC 的性能,所以第二种判断会越来越少。
三色标记法
三色标记算法是一种 JVM 中垃圾标记的算法,它可以减少 JVM 在 GC 过程中的 STW 时长,它是 CMS、G1 等垃圾收集器中主要使用的标记算法。
三色标记法将对象分为三种状态:白色、灰色和黑色。
白色:该对象没有被标记过。
灰色:该对象已经被标记过了,但该对象的引用对象还没标记完。
黑色:该对象已经被标记过了,并且它的全部引用对象也都标记完了。
三色标记法的标记过程可以分为三个阶段:初始标记、并发标记和重新标记。
初始标记:遍历所有的根对象,将根对象和直接引用的对象标记为灰色。在这个阶段中,垃圾回收器只会扫描被直接或间接引用的对象,而不会扫描整个堆。因此,初始标记阶段的时间比较短。
并发标记:在这个过程中,垃圾回收器会从灰色对象开始遍历整个对象图,将被引用的对象标记为灰色,并将已经遍历过得对象标记为黑色。并发标记过程中,应用程序线程可能会修改对象图,因此垃圾回收器需要使用写屏障(Write Barrier)技术来保证并发标记的正确性。
重新标记:重新标记的主要作用是标记在并发标记阶段中被修改的对象以及未被遍历到的对象。这个过程中,垃圾回收器会从灰色对象重新开始遍历对象图,将被引用的对象标记为灰色,并将已经遍历过得对象标记为黑色。
在重新标记阶段结束之后,垃圾回收器会执行清除操作,将未被标记为可达对象的对象进行回收,从而释放内存空间。这个过程中,垃圾回收器会将所有未被标记的对象标记为白色。
以上三个标记阶段中,初始标记和重新标记是需要 STW 的,而并发标记是不需要 STW 的。其中最耗时的其实就是并发标记这个阶段,因为这个阶段需要遍历整个对象树,而三色标记把这个阶段做到了和应用线程并发执行,大大降低了 GC 的停顿时间。
写屏障(Write Barrier)是一种编程技术,广泛应用于垃圾回收(Garbage Collection, GC)的实现中。它主要用于确保在并发环境中对象引用关系变化时,垃圾回收器能够及时捕捉到这些变化,从而正确地追踪存活对象,避免遗漏存活对象或错误地标记存活对象为垃圾