JVM有两种算法来判断对象是否存活,分别是引用计数法和可达性分析算法
引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
假设A对象引用了B对象,B对象引用了A对象,那么A和B对象的计数器就永远都是大于0。当A对象或者B对象不再使用时,使用该算法判断,A对象或B对象永远都是存活的,这就导致了A对象和B对象不能被正确回收,造成内存泄露。
可达性分析算法
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
但是,并不是说当进行完可达性分析算法后,即可证明某对象可以被GC。对象是否存活,需要两次标记:
- 第一次标记:垃圾收集器首次发现一个对象是不可达的并且该对象覆盖了
finalize方法时,会将这个对象放入一个名为finalization queue的队列中,而不是立即回收该对象。 - Java 虚拟机(JVM)中有一个专门的线程(Finalizer 线程)从
finalization queue中取出对象并调用它们的finalize方法。这个过程是异步的,且不保证finalize方法会立即执行。 - 重新标记:在
finalize方法执行期间,如果该对象重新变得可达(例如,通过在finalize方法中将其自身赋值给某个静态变量),则该对象将从finalization queue中移除,并不会被回收。 - 回收:如果在
finalize方法执行完毕后,该对象仍然不可达,则它将被垃圾收集器在下一次 GC 过程中回收。
不过现在都不提倡覆盖finalize方法,它的本意是像Cpp一样在对象销毁前执行,但是它影响了JAVA的安全和GC的性能,所以第二种判断会越来越少。
哪些内容可以作为可达性分析算法的GC Roots
GC Roots是作为可达性分析算法的起点的。要实现语义正确的可达性分析,就必须要能完整枚举出所有的GC Roots,否则就可能会漏扫描应该存活的对象,导致GC错误回收了这些被漏扫的活对象。那么,所谓“GC Roots”,就是一组必须活跃的引用。
- Thread 当前活着的线程
- 方法中的局部变量
- JNI Local - JNI方法的local变量或参数
- JNI Global - 全局JNI引用
- 被同步锁(synchronized)持有的对象
- 跨代引用的问题,会把Remembered Set也作为GC Root。
可达性分析算法的缺陷
- STW(Stop The World)的时间长 可达性分析算法需要对程序进行全局分析,因此时间复杂度较高,可能需要很长的时间才能 完成分析,并且整个过程都是STW的,所以对应用的整体性能有很大影响。这也使得可达性分析算法难以适用于大型程序的分析。解决这个问题的主要方法是三色标记法。
- 内存消耗 可达性分析算法需要存储程序中所有的对象和它们之间的引用关系,这些信息需要占用大量的内存空间。对于大型程序,如果要进行完整的可达性分析,需要存储的对象数量和引用关系数量都非常大,可能会导致内存空间不足或者程序性能下降的问题。


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



