判断Java对象是否存活的方法

时间:2022-12-27 18:26:59

0. 前言

本文参考于《深入理解Java虚拟机:JVM高级特性与最佳实践》

堆中几乎存放着Java世界中所有的对象实例,垃圾收集器在对堆回收之前,第一件事情就是要确定这些对象哪些还“存活”着,哪些对象已经“死去”(即不可能再被任何途径使用的对象),哪用什么办法去确认这些对象存活与否。在主流的商用程序语言中(Java和C#等),都是使用可达性分析算法(Reachability Analysis)来判断对象是否存活的,但是又有很多人又认为是用引用计数算法(Reference Counting)来判断。接下来我会分别介绍这两种算法。

1. 引用计数算法

很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器减1;任何时刻计数器都为0的对象就是不可能再被使用的。

引用计数算法(Reference Counting)的实现简单,判断效率也很高,在大部分情况下它都是一个不错的算法。但是Java语言中没有选用引用计数算法来管理内存,其中最主要的一个原因是它很难解决对象之间相互循环引用的问题。

例如下面这段代码,对象objA和objB都有字段instance,赋值令objA.instance=objB及objB.instance=objA,除此之外这两个对象再无任何引用,实际上这两个对象都已经不能再被访问,但是它们因为相互引用着对象方,所以它们的引用计数都不为0,于是如果是使用引用计数算法的话就GC收集器就不会回收它们。

public class ReferenceCountingGC {

private ReferenceCountingGC instance = null;
private static final int _1m = 1024*1024;
//只是为了占点内存
private byte[] bigSize = new byte[2*_1m];

public static void main(String[] args) {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();

objA.instance = objB;
objB.instance = objA;

objA = null;
objB = null;

System.gc();
}
}

输出结果

[GC (System.gc()) [PSYoungGen: 6758K->632K(38400K)] 6758K->640K(125952K), 0.0008264 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 632K->0K(38400K)] [ParOldGen: 8K->537K(87552K)] 640K->537K(125952K), [Metaspace: 2755K->2755K(1056768K)], 0.0053125 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 38400K, used 333K [0x00000000d5c00000, 0x00000000d8680000, 0x0000000100000000)
eden space 33280K, 1% used [0x00000000d5c00000,0x00000000d5c534a8,0x00000000d7c80000)
from space 5120K, 0% used [0x00000000d7c80000,0x00000000d7c80000,0x00000000d8180000)
to space 5120K, 0% used [0x00000000d8180000,0x00000000d8180000,0x00000000d8680000)
ParOldGen total 87552K, used 537K [0x0000000081400000, 0x0000000086980000, 0x00000000d5c00000)
object space 87552K, 0% used [0x0000000081400000,0x00000000814864e0,0x0000000086980000)
Metaspace used 2762K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 299K, capacity 386K, committed 512K, reserved 1048576K

PSYoungGen: 6758K->632K(38400K),新生代内存从6758K到清理后的632K,很明显GC收集器回收了objA和objB,所以HotSpot虚拟机并不是采用引用计数算法来判断一个对象存活与否。

2. 可达性分析算法

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如下图所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
判断Java对象是否存活的方法

在Java语言中,可作为GC Roots的对象包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

如果上面的内容有错误的地方或者讲的不好的地方,还请大家指点一下,我好及时修改。