3.4 HotSpot的算法实现

3.2 节和3.3节从理论上介绍了对象存活判定算法和垃圾收集算法,而在HotSpot虚拟机上实现这些算法时,必须对算法的执行效率有严格的考量,才能保证虚拟机高效运行。

3.4.1 枚举根节点

从可达性分析中从GC Roots节点找引用链这个操作为例,可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中,现在很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然会消耗很多时间。

另外,可达性分析对执行时间的敏感还体现在GC停顿上,因为这项分析工作必须在一个能确保一致性的快照中进行——这里“一致性”的意思是指在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还在不断变化的情况,该点不满足的话分析结果准确性就无法得到保证。这点是导致GC进行时必须停顿所有Java执行线程(Sun将这件事情称为“Stop The World”)的其中一个重要原因,即使是在号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。

由于目前的主流Java虚拟机使用的都是准确式GC(这个概念在第1章介绍Exact VM对Classic VM的改进时讲过),所以当执行系统停顿下来后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得知哪些地方存放着对象引用。在HotSpot的实现中,是使用一组称为OopMap的数据结构来达到这个目的的,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样,GC在扫描时就可以直接得知这些信息了。下面的代码清单3-3是HotSpot Client VM生成的一段String.hashCode()方法的本地代码,可以看到在0x026eb7a9处的call指令有OopMap记录,它指明了EBX寄存器和栈中偏移量为16的内存区域中各有一个普通对象指针(Ordinary Object Pointer)的引用,有效范围为从call指令开始直到0x026eb730(指令流的起始位置)+142(OopMap记录的偏移量)=0x026eb7be,即hlt指令为止。

代码清单3-3 String.hashCode()方法编译后的本地代码


[Verified Entry Point]

0x026eb730:mov%eax,-0x8000(%esp)

……

;ImplicitNullCheckStub slow case

0x026eb7a9:call 0x026e83e0

;OopMap{ebx=Oop[16]=Oop off=142}

;*caload

;-java.lang.String:hashCode@48(line 1489)

;{runtime_call}

0x026eb7ae:push$0x83c5c18

;{external_word}

0x026eb7b3:call 0x026eb7b8

0x026eb7b8:pusha

0x026eb7b9:call 0x0822bec0;{runtime_call}

0x026eb7be:hlt