安全点

OopMap的协助下,HotSpot可以快速准确地完成GC Roots枚举。

问题

可能导致OopMap内容变化的指令非常多,如果为每一条指令都生成对应的OopMap

那将需要大量的额外存储空间,这样垃圾收集伴随而来的空间成本就会非常高昂。

安全点(Safepoint)

实际上HotSpot没有为每条指令生成OopMap,只在安全点(Safepoint)生成

由于垃圾收集而需要停止用户线程时,用户线程不能在任意的地方暂定,必须到达安全点才能暂停。

安全点的选择

安全点位置的选择基本是以“是否具有让程序长时间执行的特征”为标准指定的,如:

  • 方法调用
  • 循环跳转
  • 异常跳转

HotSpot为了避免安全点过多带来过重的负担,对循环还有一项优化,认为循环次数较少的话,

执行时间也不会太长,所以使用int或范围更小的数据类型作为索引值的循环默认是不会放置

安全点的。这种循环称为可数循环(Counted Loop),相对应地,使用long或者范围更大的

数据类型作为索引值的循环就被称为不可数循环(Uncounted Loop),将会被放置安全点。

-XX:+UseCountedLoopSafepoints可强制可数循环也放置安全点(JDK8下有Bug

如何让线程都跑到最近安全点

  • 抢占式中断(Preemptive Suspension

在垃圾收集发生时,系统首先将所有用户线程中断,如果发现

用户线程不在安全点上,就恢复这个线程,直到跑到安全点上。

几乎没有虚拟机使用这种方式。

  • 主动式中断(Voluntary Suspension

设置一个标志位,每个线程执行过程时会不停地轮询这个标识,

一旦发现标志位为真时就自己在最近的安全点上主动挂起。

轮询标志位和安全点是重合的(到了安全点就轮询一次),

另外在所有创建对象和需要在堆上分配内存的地方也会轮询,

检查是否即将发生垃圾收集,防止没有足够内存分配对象。

注意

这里不包括JNI调用的线程,执行native代码的用户线程也不需要停顿,

因为native代码一般不会改变Java对象的引用关系,没必要挂起它们来等待垃圾回收。

HotSpot使用内存保护陷阱的方式,把轮询操作精简至只有一条汇编指令。// TODO 这是啥