HotSpot算法细节


1. 根节点枚举

类加载完成后,HotSpot使用一组OopMap数据结构来记录对象内对应偏移量上对应的数据类型,在即时编译过程中,也会在特定位置记录下栈里和寄存器里哪些位置是引用。这样,垃圾收集器在进行GC Roots扫描时就不要从方法区等GC Roots开始查找,优化引用链构建效率。

OopMap会带来其他问题:如果每条指令都生成OopMap将需要大量额外的空间,此时引出了安全点。

2. 安全点

安全点主要针对程序获取CPU时间片,执行进行垃圾收集的情况。

HotSpot只在安全点位置才会生成OopMap,即只有在安全点才能开始垃圾收集

安全点的设定是按照是否具有让程序长时间执行的特性为标准的。

如何在垃圾收集发生时让所有线程都跑到最近的安全点,然后停顿下来,有两种方案:

  • 抢先式中断(几乎没有虚拟机使用)
  • 主动式中断

2.1 抢先式中断

在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,过一会儿再中断,直到跑到安全点上

2.2 主动式中断

当垃圾收集需要中断线程时,设置一个中断标志位,各个线程执行过程时会不停地主动去轮询这个标志位,一旦发现中断标志位为真时就自己在最近的安全点上主动中断挂起。

HotSpot使用内存保护陷阱的方式,将轮询操作精简至只有一条汇编指令的程度

3. 安全区域

安全区域针对程序未获取CPU时间片,未执行的情况,如:Sleep

安全区域指能够保证在某一段时间片段中,引用关系不会发生变化。

当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入安全区域,此时虚拟机发起垃圾收集时就不会管这些线程。当线程要离开安全区域时,它要检查虚拟机是否已经完成GC Roots枚举如果完成,用户线程继续执行如果未完成,用户线程等待,知道收到可以离开安全区域的信号为止

4. 卡表

image.png

为了避免对象跨代引用导致将整个老年代加入GC Roots枚举范围的问题,HotSpot使用卡表数据结构。

卡表的每个比特位记录了该区域是否存在跨代引用的对象。

如果卡页中包含跨代引用的对象,卡表位变为1,即元素变脏。因此,卡表位为0时所在区域的老年代对象,一定不包含跨代引用的对象。

垃圾收集器只需将卡表位为1对应的内存页加入GC Roots枚举范围即可

4.1 卡表维护(写屏障)

此处的写屏障不同于低延迟垃圾收集器中的读屏障与volatile对应的内存屏障

写屏障可看做针对引用对象赋值操作的一个AOP切面,分为写前屏障、写后屏障。

HotSpot字节码解释器和即时编译器使用写屏障维护卡表。每次执行更新引用的字节码时,都是执行写屏障。

写屏障会是线程增加一些性能开销,但代价远低于Minor GC时扫描整个老年代,因此,写屏障使得整体垃圾收集效率提高,吞吐量也有所改善

除了性能开销,卡表在高并发场景还面临伪共享问题。可通过添加卡表更新条件判断的方式避免此问题。

JDK7之后,提供了虚拟机参数-XX:+/-UseCondCardMark来决定是否开启卡表更新条件判断。

开启之后,会增加一次额外判断开销,但是避免了伪共享。具体应用应以实际测试为准。

 

 

 

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部