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