很多教科书判断对象是否存活的算法是这样的:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用。很多应届生和一些有多年工作经验的开发人员,他们对于这个问题给予的都是这个答案。
客观地说,引用计数算法的实现建安,判定效率也很高,在大部分情况下它都是一个不错的算法,也有一些比较著名的应用案例,例如微软COM(Component Object Model)技术、使用ActionScript3的FlashPlayer、Python语言以及在游戏脚本领域中被广泛引用的Squirrel都使用了引用计数算法进行内存管理。但是,Java语言中没有选用引用计数算法来管理内存,其中最主要的原因他很难解决对象之间的相互循环引用的问题。
下面的代码,对象objA和objB都有字段instance,赋值令objA.instance = objB及objB.instance = objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是他们因为互相引用着对方,导致他们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们。
package gc; /** * 引用计数算法GC * @author Madison * @date 2014-7-12 * testGC()方法执行后,objA和objB会不会被GC呢 */ public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; //这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过 private byte[] bigSize = new byte[2 * _1MB]; public static void testGC() { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc(); } }
运行结果:
[Full GC (System) [Tenured: 0K->153K(10944K), 0.0052420 secs] 4409K->153K(15872K), [Perm : 381K->381K(12288K)], 0.0052783 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 4992K, used 179K [0x241b0000, 0x24710000, 0x29700000)
eden space 4480K, 4% used [0x241b0000, 0x241dcda8, 0x24610000)
from space 512K, 0% used [0x24610000, 0x24610000, 0x24690000)
to space 512K, 0% used [0x24690000, 0x24690000, 0x24710000)
tenured generation total 10944K, used 153K [0x29700000, 0x2a1b0000, 0x341b0000)
the space 10944K, 1% used [0x29700000, 0x29726430, 0x29726600, 0x2a1b0000)
compacting perm gen total 12288K, used 381K [0x341b0000, 0x34db0000, 0x381b0000)
the space 12288K, 3% used [0x341b0000, 0x3420f4e0, 0x3420f600, 0x34db0000)
ro space 10240K, 55% used [0x381b0000, 0x38733dd8, 0x38733e00, 0x38bb0000)
rw space 12288K, 55% used [0x38bb0000, 0x39256cd0, 0x39256e00, 0x397b0000)
从运行结果中可以清楚看到GC日志中包含“4409K->153K”,意味着虚拟机并没有因为这两个对象互相引用就不回收它们,这也从侧面说明虚拟机并不是通过引用计数器算法来判断对象是否存活的。
欲知后事如何,且听下回分解
JVM【第十回】:【判断对象已死之引用计数算法】