个人博客
从垃圾识别到收集器:详细聊聊Java的GC | iwts’s blog
前言
聊GC,自然离不开JVM内存模型,建议先了解JVM内存模型相关内容,或者最起码了解堆相关的内容,GC主要处理的就是堆。
这里会从垃圾识别算法->GC算法->JVM 垃圾收集器,从发展演进的过程详细聊聊,内容会比较长。
垃圾识别算法
引用计数法
简单地说,就是对象被引用一次,在它的对象头上加一次引用次数,如果没有被引用(引用次数为 0),则认为此对象可回收。
看起来比较简单,只要没有变量引用这个具体的对象,那么就认为这个对象是没有用的,可以被GC回收掉。
但是这样可能会造成一个问题:循环引用。
循环引用问题
给个代码:
public class TestRC {
TestRC instance;
public TestRC(String name) {
}
public static void main(String[] args) {
// 第一步
TestRC a = new TestRC("a");
TestRC b = new TestRC("b");
// 第二步
a.instance = b;
b.instance = a;
// 第三步
a = null;
b = null;
}
}
那么变量的引用过程:
最后导致了这样的情况,A和B互相引用,但是本质上这两个对象都应该是垃圾,因为完全没有被正经使用了。
采用引用计数法时,这个问题无解,所以目前GC算法都不会使用这个算法。
可达性算法
目前JVM一般都是采用这个方法来判断对象是否存活。
可达性算法的原理是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点。
这样通过 GC Root 串成的一条线就叫引用链,直到所有的结点都遍历完毕。
如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为垃圾,会被 GC 回收。
文字可能略有难懂,看图:
等于说JVM本身维护了一个GC Roots的集合,每个GC Roots都有一条具体的引用链,只有在引用链上的对象才不会被回收。
那么对于循环引用的问题,虽然两个变量在循环引用,但是由于已经不在GC Roots的引用链上,所以依旧会被回收掉。
finalize 方法
算是一次误杀补偿的机会。当一次GC扫描中,发现一个对象没有在引用链上,那么就会先对这个对象执行一次finalize方法,看是否有GC Roots能和对象进行关联。如果有的话,就不对该对象执行GC。
但是finalize对于每个对象是只能执行一次的。如果该对象执行方法后没有被回收,那么第二次GC的时候又发现了这个对象,那么这次就不给机会了,直接回收掉。
GC Roots的选择
综上,GC Roots的选择就非常重要了。而GC Roots本身其实也是一个对象,但是这个对象的引用方,即变量,要级别够高,最好是方法/应用从最开始就出现的对象。所以GC Roots的选择有以下几种:
虚拟机栈中引用的对象
即栈帧中的本地变量表中的变量。会随着弹栈自己消失,消失前基本保证整个方法都在用。
public class Test {
public void test() {
Test a = new Test(