java虚拟机总共分为五个区域,其中三个是线程私有:程序计数器,虚拟机栈,本地方法栈,两个是线程共享:堆,方法区。线程私有的区域等到线程结束时(栈帧出栈时)会自动被释放,空间比较容易清理。而线程共享的java堆和方法区中的空间较大而且没有线程的回收容易产生很多垃圾信息,GC垃圾回收真正关心的就是这部分。
java堆和方法区主要存放各种类型的对象(方法区中也存储一些静态变量和全局常量等信息),那么我们在使用GC对其进行回收的时候首先要考虑的就是如何判断一个对象是否应该被回收。也就是要判断一个对象是否还有其他的引用或关联使得这个对象处于存活的状态。我们需要将不在存活状态的所有对象标记出,以便于GC进行回收。
判断对象是否存活有两种比较常见的方法:引用计数法与可达性分析算法。
引用计数法
每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。(如果一个对象A持有对象B,而对象B也持有一个对象A,那发生了类似操作系统中死锁的循环持有,这种情况下A与B的counter恒大于1,会使得GC永远无法回收这两个对象)
所以java并不采用这种方式进行对象存活判断
测试:现在run Configuration中加入:-verbose:gc -XX:+PrintGCDetails。了解gc信息
public class Main {
private Object instance;//创建一个对象的引用
//唯一的意义是占点内存,以便能在GC日志中看清楚是否被回收过
public Main() {
byte[] m = new byte[20 * 1024 * 1024];
}
public static void main(String[] args) {
Main m1 = new Main();
Main m2 = new Main();
//创建循环引用
m1.instance = m2;
m2.instance = m1;
m1 = null;
m2 = null;
//在这行发生GC,m1和m2是否能被回收
System.gc();
}
}
[GC (System.gc()) [PSYoungGen: 22477K->664K(38400K)] 42957K->21152K(125952K), 0.0007999 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 664K->0K(38400K)] [ParOldGen: 20488K->529K(87552K)] 21152K->529K(125952K), [Metaspace: 2664K->2664K(1056768K)], 0.0038563 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 38400K, used 333K [0x00000000d5d00000, 0x00000000d8780000, 0x0000000100000000)
eden space 33280K, 1% used [0x00000000d5d00000,0x00000000d5d534a8,0x00000000d7d80000)
from space 5120K, 0% used [0x00000000d7d80000,0x00000000d7d80000,0x00000000d8280000)
to space 5120K, 0% used [0x00000000d8280000,0x00000000d8280000,0x00000000d8780000)
ParOldGen total 87552K, used 529K [0x0000000081600000, 0x0000000086b80000, 0x00000000d5d00000)
object space 87552K, 0% used [0x0000000081600000,0x0000000081684458,0x0000000086b80000)
Metaspace used 2670K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
从运行结果可以看到,GC日志中包含“22477K->664K”意味着虚拟机并没有因为这两个对象相互引用而就不回收他们,从侧面说明了jdk8里面的虚拟机并不是通过引用计数算法来判断对象是否存活的。
可达性分析算法
在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。
这个算法的基本思路就是:通过一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。
那么那些点可以作为GC Roots呢?一般来说,如下情况的对象可以作为GC Roots:
- 虚拟机栈(栈桢中的本地变量表)中的引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(Native方法)的引用的对象