学习目标
在学习JVM中,我们重点在于认识和了解GC这块,而GC在前面Java基础学习之JVM篇:说说栈和堆的区别
我们重点和大家一起学习了Java虚拟机的内存模型,在Java基础学习之JVM篇:说说STW、吞吐量
中当然我们基本知道了GC的基本原理和作用,那么本节我们可以来一起了解GC的实现方式,也可以说是GC的算法实现。
- 了解引用计数
- 为什么需要三色标记而不是双色标记
- 三色标记如何处理mutation
引用计数
什么是引用计数?顾名思义就是这个对象被引用的次数,例如对象A被其它对象引用了两次,即计数2。
只要计数>0的将不会被GC回收,而计数为0的将被GC回收。例如上图中的对象B和C没有被其它对象引用,计数为0,那么在下次GC时将被回收。
其实引用计数在历史长河中存在了很长一段时间,但是它有着明显的缺陷。例如下面的引用:
public class EmptyObject {
EmptyObject ref=null;
public static void main(String[] argv){
EmptyObject objectA=new EmptyObject();
EmptyObject objectB=new EmptyObject();
objectA.ref=objectB;
objectB.ref=objectA;
objectA=null;
objectB=null;
}
}
这里就是关于循环引用的问题,我们知道如果采用引用计数法,myObject1和myObject2将不能被回收,因为他们的引用计数无法为零,可以理解为如下图:
次数A和B引用计数都为2,当我们将objectA和objectB置为null后:
次数A和B计数为1,都不能被GC回收,陷入死循环。甚至在多线程中,引用计数都显得很不靠谱,引用计数也许在一个线程中被误以为清零,在另一个线程又开始计数,造成计数的不一致。那么是不是需要优化呢?
双色标记
我们在引用计数方法,发现会出现上述问题后,便有了一系列优化过程。比如Root tracing遍历即根节点遍历法,如果有对象的节点有联通路径到达根节点,那么即为有效,否则将会被回收,例如下图红色为不可达,将被回收。
其实可以分为两个阶段,所有节点初始过程都标记为红色,第一阶段在遍历过程中进行标记mark,可达标记为白色,第二阶段为sweep,包含需要finalize和不需要finalize的类。其实这里可以理解就是一种双色标记。真正的双色标记清除就是如此:
但是用着用着发现又有新的问题出现,比如下面的变动:
GC此时一边打扫,标记,清除,但这是其它线程又把原来的引用改动了。特别时在GC标记后,准备Sweep时,那问题就严重了,例如此时B引用的又指向了C。
三色标记
我们可以将已被Mark的节点记录状态,当被Mark的还被改了就是Mutation状态。记录到了这种状态后我们需要重新遍历标记。
那么我么怎么去处理Mutatin状态的节点去重新Mark呢?
可以添加一种颜色,就是一种状态,理解为未完成的任务,比如标记灰色状态的就是需要ReMark的。比如下面从C开始遍历,能到达的全部标黑。