OopMap
垃圾回收时,如何找到垃圾?
在可达性分析算法中从GC Roots集合
找引用链分析对象是否可达。
固定可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表),Java应用越做越庞大,光是方法区的大小就常有数百上千兆,里面的类、常量等更是恒河沙数,若要逐个检查以这里为起源的引用肯定得消耗不少时间。
迄今为止,所有收集器在根节点枚举
这一步骤时都是必须暂停用户线程,因此根节点枚举也会面临相似的“Stop The World”的困扰。
根节点枚举必须在一个能保障一致性的快照中才得以进行——“一致性”:整个枚举期间执行子系统看起来就像被冻结在某个时间点上
,不会出现分析过程中,根节点集合的对象引用关系还在不断变化的情况,是导致垃圾收集过程必须停顿所有用户线程的其中一个重要原因。
实际上虚拟机应当是有办法直接得到哪些地方存放着对象引用的,HotSpot的解决方案中,是使用一组称为OopMap的数据结构
来达到这个目的。(一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。)
安全点
在OopMap的协助下,HotSpot可以快速准确地完成GC Roots枚举,但没有为每条指令都生成OopMap。
“特定的位置”记录了这些信息,这些位置被称为安全点(Safepoint)
。
安全点是以 “是否具有让程序长时间执行的特征”为原则进行选定的,所以方法调用、循环跳转、异常跳转这些位置都可能会设置有安全点
。
有了安全点的设定,也就决定了用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始垃圾收集,而是强制要求必须执行到达安全点后才能够暂停。(只有在安全点位置才能进行垃圾收集
)
垃圾收集刚开始的时候需要先获取所有根节点(根节点枚举),而根节点的获取依赖所有线程抵达安全点。
可数循环&不可数循环
HotSpot虚拟机为了避免安全点过多带来过重的负担,对循环还有一项优化措施,认为循环次数较少的话,执行时间应该也不会太长,所以使用int类型或范围更小的数据类型作为索引值的循环默认是不会被放置安全点
。这种循环被称为可数循环(CountedLoop)。
使用long或者范围更大的数据类型作为索引值的循环
就被称为不可数循环(Uncounted Loop),将会被放置安全点
【可数循环不会被放置到安全点,不可数循环会被放置到安全点
】
通常情况下这个优化措施是可行的,但循环执行的时间不单单是由其次数决定,如果循环体单次执行就特别慢,那即使是可数循环也可能会耗费很多的时间。
如果由可数循环耗时时间较长,安全点导致长时间停顿,可以将循环索引的数据类型从int改为long即可
。
现象:
当垃圾收集发生时,如果有线程刚好执行到可数循环(较耗时)时,则必须等待循环全部跑完才能进入安全点,此时其他线程也必须一起等着,所以从现象上看就是长时间的停顿。
出现可数循环比较耗时,可以将循环索引的数据类型从int改为long。 将可数循环变为不可数循环,放置到安全点位置即可。