当JVM判断对象不再存活的时候,便会在下一次GC时候将该对象回收掉,为堆腾出空间,那么JVM如何判断对象是否存活呢?
JVM有两种算法来判断对象是否存活,分别是引用计数法和可达性分析算法
循环引用会导致对象无法被回收,最终会导致内存泄漏及内存溢出
但是,并不是说当进行完可达性分析算法后,即可证明某对象可以被GC。对象是否存活,需要两次标记:
finalize()
方法被覆盖并且没有执行过,则放在F-Queue队列中等待执行(不一定会执行),如果一段时间后该队列的finalize()
方法被执行且和GC Roots关联,则移出“即将回收”集合。如果仍然没有关联,则进行第二次标记,才会对该对象进行回收不过现在都不提倡覆盖finalize
方法,它的本意是像Cpp一样在对象销毁前执行,但是它影响了JAVA的安全和GC的性能,所以第二种判断会越来越少
GC roots是作为可达性分析算法的起点的。要实现语义正确的可达性分析,就必须要能完整枚举出所有的GC Roots,否则就可能会漏扫描应该存活的对象,导致GC错误回收了这些被漏扫的活对象。那么,所谓“GC Roots”,就是一组必须活跃的引用。
那么,有哪些引用是一定活跃的呢?看下下面这些是不是都符合这个条件:
以上,比如系统类加载器加载的对象、活着的线程、方法中的本地变量、被synchronized锁定的对象这些,都是符合活跃的引用这个条件的!
除了这些, 还有一种,基本上很少有人提到的,大家去看网上的所有关于介绍GC Root的八股文,基本上没人提的,那就是为了解决跨代引用的问题,会把Remembered Set也作为GC Root。
虽然可达性分析算法相比于引用计数法要好很多,但是他也不是毫无缺点的。这种算法主要存在以下几个不足:
可达性分析算法需要对程序进行全局分析,因此时间复杂度较高,可能需要很长的时间才能完成分析,并且整个过程都是STW的,所以对应用的整体性能有很大影响。这也使得可达性分析算法难以适用于大型程序的分析。所以一些常见的回收器都会使用一些优化技术来减少可达性分析的时间和开销,如增量标记、增量拷贝等。
解决这个问题,主要是依赖三色标记法
可达性分析算法需要存储程序中所有的对象和它们之间的引用关系,这些信息需要占用大量的内存空间。对于大型程序,如果要进行完整的可达性分析,需要存储的对象数量和引用关系数量都非常大,可能会导致内存空间不足或者程序性能下降的问题。