在垃圾回收中,经常会STW,为了首先STW,jvm设计了安全点(safepoint)。那么什么是安全点?线程什么时候进入和离开安全点呢?
安全点概念
安全点:安全点可以理解为一些特殊位置,当代码执行到这些特殊位置时,则当前虚拟机的状态是安全可控的。这里的安全可控是指,虚拟机可以通过VM线程找到活跃对象,能够检查或更新Mutator线程状态等。
当Mutator达到安全点后,可以主动放弃CPU,让VM线程执行。让Mutator在安全点停止的原因有2个:1. 让VMThread能够原子的运行,不受Mutator的干扰,2. 在安全点停止更容易实现。当需要STW时,会产生一个VM_Operation并放入VMThread队列中,VMThread线程会循环处理这个队列里的请求。但在真正处理VM_Operation前需进入安全点,之后需要恢复安全点。代码如下:
void VMThread::loop() {
assert(_cur_vm_operation == NULL, "no current one should be executing");
while(true) {
....
// 进入安全点
SafepointSynchronize::begin();
//执行VM_Operation
evaluate_operation(_cur_vm_operation);
// 退出安全点
SafepointSynchronize::end();
}
}
}
进入安全点的方式
G1并发线程进入安全点
像G1新引入的ConcurrentRefineThread、ConcurrentMarkThread和G1StringDedupThread线程,当这些线程在内部工作时会调用join,离开时候调用leave,主动放弃时调用yield。其中,用join判断VMThread是否发出了安全点的请求,若发出则等待。等并发线程leave后,VMThread就可以工作了。并发线程在leave后,在做下一次工作时,又会再调用join,若VMThread发出请求,
就会进入等待。解释线程进入安全点
如果Mutator是解释执行,即通过模板解释器执行字节码,那么该如何放弃CPU,JVM提供了一个正常的指令派发表和异常指令派发表,当需要进入安全点时,JVM会用异常指令派发表代替正常的指令派发表,那么线程执行完当前指令后就会进入异常指令派发表,异常指令派发表所有的栈顶状态缓存都会执行进入安全点的方法,从而进入安全点。编译线程进入安全点
编译线程进入安全点是借助于Linux信号完成的。JVM在初始化时会生成一个全局的轮询页面。JIT在编译时会插入额外的汇编代码去轮询这个页面的状态,若不可读则产生一个SIGSEGV,JVM则会处理这个信号并最终将线程阻塞。正在执行本地代码的线程进入安全点
如果线程正在运行本地代码,是无法访问Java对象的,此时VMThread可与本地代码并发运行。当此线程切换到Java代码执行的时候,那么就需要让这个线程暂停。JVM是通过在设置标志位来实现的,当切换到Java代码时,若发现标志位已设置则让自己暂停。