很多教科书判断对象是否存活的算法是这样的:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。但是Java语言中没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间的相互循环引用的问题。
举个简单的例子,对象objA和objB都有字段instance,赋值令objA.instance = objB及objB.instance = objA,除此之外,这两个对象再无引用,实际上这两个对象已经不可能再被访问,但是因为它们互相引用着对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们。
例:
package jvm;
/**
* -XX:+PrintGCDetails
*
* @author Poison
*
*/
class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
/**
* 这个成员属性的唯一意义就是占点内存,yi'bian3能在GC日志中看清楚是否被回收过
*/
private byte[] bigSize = new byte[2 * _1MB];
public static void main(String[] args) {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
//假设在这行发生GC,那么objA和objB是否能被回收?
System.gc();
}
}
运行结果:
[GC [PSYoungGen: 5079K->600K(18944K)] 5079K->600K(60928K), 0.0021405 secs] [Times: user=0.05 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 600K->0K(18944K)] [ParOldGen: 0K->465K(41984K)] 600K->465K(60928K) [PSPermGen: 2570K->2569K(21504K)], 0.0139597 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 18944K, used 819K [0x00000000eb600000, 0x00000000ecb00000, 0x0000000100000000)
eden space 16384K, 5% used [0x00000000eb600000,0x00000000eb6ccdf8,0x00000000ec600000)
from space 2560K, 0% used [0x00000000ec600000,0x00000000ec600000,0x00000000ec880000)
to space 2560K, 0% used [0x00000000ec880000,0x00000000ec880000,0x00000000ecb00000)
ParOldGen total 41984K, used 465K [0x00000000c2200000, 0x00000000c4b00000, 0x00000000eb600000)
object space 41984K, 1% used [0x00000000c2200000,0x00000000c2274560,0x00000000c4b00000)
PSPermGen total 21504K, used 2579K [0x00000000bd000000, 0x00000000be500000, 0x00000000c2200000)
object space 21504K, 11% used [0x00000000bd000000,0x00000000bd284dd8,0x00000000be500000)
从运行结果中可以清楚地看到GC日志中包含“5079K->600K”,意味着虚拟机并没有因为这两个对象互相引用就不回收它们,这也从侧面说明虚拟机并不是通过引用计数算法来判断对象是否存活的。