Java GC 内存回收机制详解(二)GC Roots 和 可达链

三. 什么是GC Roots?
在Java语言中,可作为GC Roots的对象包括下面几种:
  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。
凡事被常量、静态变量、全局变量、运行时方法中的变量直接引用的对象,原则上不能被GC释放。
参见下图:
Java GC 内存回收机制详解(二)GC Roots 和 可达链_第1张图片
我们常说的垃圾回收,就是回收图中红色圈圈住的堆的部分。换句话说,是回收我们用new,分配在堆中的对象。这些常亮、全局变量、静态变量,以及方法内的局部变量,是存储在上图的蓝色区域中。

堆中对象在正常情况下,可以通过常亮、全局变量、静态变量,以及方法内的局部变量中保存的该对象的引用间接访问。如果堆中的对象,没有任何引用可以间接访问,这个对象就是可以被回收的。这就是我们平时写程序,为什么要及时的赋NULL的原因。

上述保存在蓝色部分区域中的对象,在Java GC中,是一种特殊的对象,我们称之为GC Root。用一个大白话说,常亮、全局变量、静态变量,以及方法内的局部变量,在GC视野里,都是GC Root对象。当堆中的对象,在任何上述的GCRoot中,有引用可以指向时,我们称之为对象可达(这个对象会被用到,不可释放)。反之为不可达(这个对象反正无法被访问到,可以被回收)。

小结:
New出来的对象,放在堆里。堆满了,程序就挂了(OOM)。因此需要回收堆中的不用对象。所有New出来的对象,需要通过GC Root对象中保存的引用来间接访问。如果一个堆中的对象,没有任何 GC Root对象中有引用指向,这个对象就不可达,可以被GC回收掉。常见的GC对象,是常亮、全局变量、静态变量,以及方法内的局部变量。上述的GC对象存储在方法区和方法栈中。

四. 什么是可达链?
先看示意代码:
1. fun(...){
2. v a = new A;
3. a.b = new B;
4. a.c = new C;
5. a.b.d = new D;
6. a.b.d.c = a.c;
7. a.c.b = a.b;
8. a.c = null;
9. a.b = null;
10. ...............
100. }
图中,变量a, 是方法里的局部变量,存在于在方法整个生命周期(大括号)中。
这个方法运行时,所有和此局部变量a有关系的变量,都不可以被释放掉。按照前面对GCRoot的介绍,可以理解为a是一个GCRoot对象。
当程序运行完第5行时,对象之间的引用关系如下图所示:
Java GC 内存回收机制详解(二)GC Roots 和 可达链_第2张图片
此时,每个对象可达,ABCD均不可释放。

当程序运行完第7行时,对象之间的引用关系如下图所示:
Java GC 内存回收机制详解(二)GC Roots 和 可达链_第3张图片
此时,每个对象可达,ABCD均不可释放。

当程序运行完第8行时,对象之间的引用关系如下图所示:
Java GC 内存回收机制详解(二)GC Roots 和 可达链_第4张图片
此时,每个对象可达,ABCD均不可释放。

当程序运行完第9行时,对象之间的引用关系如下图所示:
Java GC 内存回收机制详解(二)GC Roots 和 可达链_第5张图片
此时,BCD三个对象无任何来自GCRoot的引用。可以被GC释放掉。A不能被释放。
当程序运行完第100行时,方法结束,方法栈被弹出,局部变量a不存在,对象A也无法触达,A也会被回收掉。

*小结:
从”GC roots”开始扫描(这里的roots包括线程栈、静态常量等),给能够沿着roots到达的对象标记为”live”,最终所有能够到达的对象都被标记为”live”,而无法到达的对象则为”dead”。所有非"live"的对象,都有可能被GC回收掉。另外,可以看出,这种回收算法的效率,是存活对象的数量是线性相关的。所以,不要使用大量的小的,存活期长的活动对象,而且一旦不用,立即赋值null,是一个影响效率的好习惯。

*注解:
这里为了便于理解,将GCRoot和局部变量等同,实际情况复杂一些,GC Root通常是一组特别管理的指针,这些指针是tracing GC的trace的起点,GC会通过Root,按照示例中的做法,来标记对象是否可以被回收掉。这种标记的链条,就是可达链。

你可能感兴趣的:(Java核心技术)