在进行垃圾回收之前,第一件要做的事情就是要判断哪些对象还存活着,以便于回收非存活状态的对象。那如何去判断一个对象是否存活呢?
本文带领大家深入探究两种算法,再也不需要应付面试了,深入了解底层逻辑后,你将完全掌握如何判定对象是否存活。
引用计数算法
每一个对象都会添加一个引用计数器,当有一个地方引用它时,该对象的引用计数器就加1,当不引用的时候,该计数器减1,当计数器为0的时候,就代表该对象不可能再被使用。
String str = new String("this is a string");
String str = null;
该判定对象存活算法在Python和微软某些技术领域有被使用到,但在Java领域,主流的虚拟机例如Hotspot就没有使用这个算法,因为单纯的为每个对象做引用计数会存在问题:对象之间的循环依赖。
如果一个对象A依赖了对象B,对象B依赖了对象A,那么因为它们相互引用着对方,导致引用计数器计数都不为0,就无法回收它们。
A a = new A();// 步骤1 A实例对象被a引用 A实例对象计数器 + 1
B b = new B();// 步骤2 B实例对象被b引用 B实例对象计数器 + 1
a.setB(b);// 步骤3 B实例对象被A实例对象属性引用 B实例对象计数器 + 1
b.setA(a);// 步骤4 A实例对象被B实例对象属性引用 A实例对象计数器 + 1
a = null;// 步骤5 A实例对象引用计数器 - 1
b = null;// 步骤6 B实例对象引用计数器 - 1
步骤1~4
步骤5~6
由此可见,实例对象A和实例对象B因为引用计数器不为0,所以无法被回收。
可达性分析算法
那主流的Java虚拟机如HotSpot是用什么方法识别一个对象是否存活的呢?
基本思想是通过一系列被称之为”GC Roots“的根对象作为起始节点集,从这些根节点开始,根据引用关系向下搜索,搜索过程所走过的路径称之为”引用链“,如果一个对象到RC Roots没有任何引用链相连,则表明这个对象不可达。
如上图所示,obj7、obj8和obj9虽然互相引用着,但它们没有和其中任何一个GC Roots是可达的关系,所以会被认为是可回收的对象。
除了Java虚拟机在使用这套方案判定对象是否可达外,其他还有c#等也在使用这套算法,那在Java体系里,哪些对象可以被当作GC Roots集合中的一个呢?
据统计,包括以下几种可以当作GC Roots:
1、虚拟机栈中引用的对象,例如方法的入参、局部变量、临时变量等。
2、方法区中类静态属性引用的对象。
3、方法去中常量引用的对象,例如字符串常量池中的引用。
4、本地方法栈JNI(Java Native Interface)引用的对象(深入了解过源码的同学应该见到过)。
5、Java虚拟机内部的引用,如基本数据类型对应的Class对象,常驻异常对象如NullPointException、OOM等。
6、被synchronized关键字持有的对象。
7、等等。
除了这些固定的GC Root Set外,还需要根据用户选择的垃圾收集器不同,以及回收的内存区域不同,适应性的选择其他类型的对象作为RC Roots临时加入,这样才能保证可达性分析最后的正确性。
可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中才能够进行分析,也就是说必须要冻结用户线程的运行。**为什么需要用户线程停顿?能不能解决或者降低这个时间?**洪爵下期文章来告诉大家!
愿每个人都能带着怀疑的态度去阅读文章并探究其中原理。
道阻且长,往事作序,来日为章。
期待我们下一次相遇!