堆中存放了几乎所有的对象实例,垃圾收集器对堆进行回收时,第一件事就是判断那些对象已死,那些对象还活着。
判断对象是否活着有以下几种方法:
1.引用计数算法(Reference Counting)
给对象一个引用计数器,每当一个地方引用它时就加1,当引用失效时,就减1,任何时刻计数器为0时对象就被不可能再被使用。
客观地说,引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法,也有一些比较著名的应用案例,例如微软COM(Component Object Model)技术、使用ActionScript3的FlashPlayer、Python语言以及在游戏脚本领域中被广泛引用的Squirrel都使用了引用计数算法进行内存管理。但是,Java语言中没有选用引用计数算法来管理内存,其中最主要的原因他很难解决对象之间的相互循环引用的问题。
下面的代码,对象objA和objB都有字段instance,赋值令objA.instance = objB及objB.instance = objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是他们因为互相引用着对方,导致他们的引用计数都不为0,于是引用计数算法无法通知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”,意味着虚拟机并没有因为这两个对象互相引用就不回收它们,这也从侧面说明虚拟机并不是通过引用计数器算法来判断对象是否存活的。
2.根搜索算法(GC Roots Tracing)
通过一系列的“GC Roots"对象作为起始点,从这些节点开始向下搜索,搜索所通过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连接(图论中就是GC Roots到这个对象不可达)时,则证明该对象是不可达的。
在Java语言里,可作为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中的引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(即一般说的Native方法)的引用的对象
客观地说,引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法,也有一些比较著名的应用案例,例如微软COM(Component Object Model)技术、使用ActionScript3的FlashPlayer、Python语言以及在游戏脚本领域中被广泛引用的Squirrel都使用了引用计数算法进行内存管理。但是,Java语言中没有选用引用计数算法来管理内存,其中最主要的原因他很难解决对象之间的相互循环引用的问题。
下面的代码,对象objA和objB都有字段instance,赋值令objA.instance = objB及objB.instance = objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是他们因为互相引用着对方,导致他们的引用计数都不为0,于是引用计数算法无法通知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”,意味着虚拟机并没有因为这两个对象互相引用就不回收它们,这也从侧面说明虚拟机并不是通过引用计数器算法来判断对象是否存活的。