安全点
在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 这是啥