标记、清除算法是如何处理循环引用

目前的GC算法有两个大分类:引用计数可达性。下面来说说引用计数法是如何统计所有对象的引用计数的,再对比分析可达性算法是如何解决引用技术算法的不足,好比说循环引用。

循环引用
举个栗子:有两个对象,A引用B,B引用A。
这种情况下两个对象都存在引用,那么是否这两个对象就无法被GC了呢?先卖个关子,让我们记住这种情况,带着疑问往下读,接下来了解一下GC算法的一些概念。

GC采用的算法
1.引用计数算法:每个对象都有一个引用计数器,当对象被引用时计数器+1,对象引用失效的时候,引用计数器-1,当计数为0的时候,意味着此对象是一个垃圾对象,可以被gc了。

2.可达性算法:从GC Roots作为起点开始搜索,对于GC Roots无法到达的对象便成了垃圾回收对象,随时可被gc掉。常见的可达性算法有标记、清除法。

对这两种算法有一定的了解之后,我们来观看下面这两个图(以下图片均来自知乎):

引用计数法
这里写图片描述
(图一)
这里有四个step:
· step1:Java堆中实例1引用计数+1,当前计数为1;
· step2:Java堆中实例2引用计数+1,当前计数为1;
· step3:Java堆中实例2引用计数+1,当前计数为2;
· step4:Java堆中实例1引用计数+1,当前计数为2。

四个步骤下来,堆中实例1计数=堆中实例2计数=2。

接下来再看一图:
这里写图片描述
(图二)
我们看到step1跟step2的连线已经消失了。也就是栈帧中的obj1跟obj2不再指向Java堆中的实例了。我们拆分成两个步骤(step5、step6),这时:

· step5:obj1不再指向实例1,引用数-1,当前计数为2-1=1;
· step6:obj2不再指向实例1,引用数-1,当前计数为2-1=1。

我们发现,实例1和实例2的计数都不为0,如果采用引用计数法的话,那么这两个实例所占用的内存就得不到释放了。所以,引用计数法无法解决循环利用
的gc问题。

那么,可达性算法就可以吗?它是如何操作的?

上面提到的可达性算法的概念,有个字眼-GC Roots

可以作为GC Roots的对象
· 虚拟机栈的栈帧的局部变量表所引用的对象;
· 本地方法栈的JNI所引用的对象;
· 方法区的静态变量和常量所引用的对象.

图二中,把虚拟机栈的栈帧的局部变量表看作GC Roots的话,Java堆中的实例1跟实例2就与GC Roots“失联”了。GC Roots无法到达的对象便成了垃圾回收对象,随时被gc掉。所以,可达性算法可以处理循环引用的问题,解决了引用计数算法带来的缺陷。这也是目前虚拟机基本都是采用可达性算法的原因。