safepoint是JVM里面很重要的一个概念,在很多场景下都会看到它,尤其是在GC的时候。这篇讲讲safepoint。本人不是做JVM实现研究的,很多地方只能点到为止,希望能够讲清楚这个概念,具体的细节可以自己去找资料深入研究。
safepoint 安全点顾名思义是指一些特定的位置,当线程运行到这些位置时,线程的一些状态可以被确定(the thread's representation of it's Java machine state is well described),比如记录OopMap的状态,从而确定GC Root的信息,使JVM可以安全的进行一些操作,比如开始GC。
safepoint指的特定位置主要有:
1. 循环的末尾 (防止大循环的时候一直不进入safepoint,而其他线程在等待它进入safepoint)
2. 方法返回前
3. 调用方法的call之后
4. 抛出异常的位置
之所以选择这些位置作为safepoint的插入点,主要的考虑是“避免程序长时间运行而不进入safepoint”,比如GC的时候必须要等到Java线程都进入到safepoint的时候VMThread才能开始执行GC,如果程序长时间运行而没有进入safepoint,那么GC也无法开始,JVM可能进入到Freezen假死状态。在stackoverflow上有人提到过一个问题,由于BigInteger的pow执行时JVM没有插入safepoint,导致大量运算时线程一直无法进入safepoint,而GC线程也在等待这个Java线程进入safepoint才能开始GC,结果JVM就Freezen了。
How to get Java stacks when JVM can't reach a safepoint
JVM在很多场景下使用到safepoint, 最常见的场景就是GC的时候。对一个Java线程来说,它要么处在safepoint,要么不在safepoint。
GC的标记阶段需要stop the world,让所有Java线程挂起,这样JVM才可以安全地来标记对象。safepoint可以用来实现让所有Java线程挂起的需求。这是一种"主动式"(Voluntary Suspension)的实现。JVM有两种执行方式:解释型和编译型(JIT),JVM要保证这两种执行方式下safepoint都能工作。
在JIT执行方式下,JIT编译的时候直接把safepoint的检查代码加入了生成的本地代码,当JVM需要让Java线程进入safepoint的时候,只需要设置一个标志位,让Java线程运行到safepoint的时候主动检查这个标志位,如果标志被设置,那么线程停顿,如果没有被设置,那么继续执行。
例如hotspot在x86中为轮询safepoint会生成一条类似于“test %eax,0x160100”的指令,JVM需要进入gc前,先把0x160100设置为不可读,那所有线程执行到检查0x160100的test指令后都会停顿下来
0x01b6d627: call 0x01b2b210 ; OopMap{[60]=Oop off=460}
;*invokeinterface size
; - Client1::main@113 (line 23)
; {virtual_call}
0x01b6d62c: nop ; OopMap{[60]=Oop off=461}
;*if_icmplt
; - Client1::main@118 (line 23)
0x01b6d62d: test %eax,0x160100 ; {poll}
0x01b6d633: mov 0x50(%esp),%esi
0x01b6d637: cmp %eax,%esi
聊聊JVM(五)从JVM角度理解线程 说了JVM中的线程类型,其中提到VMThread。VMThread会一直等待直到VMOperationQueue中有操作请求出现,比如GC请求。而VMThread要开始工作必须要等到所有的Java线程进入到safepoint。JVM维护了一个数据结构,记录了所有的线程,所以它可以快速检查所有线程的状态。当有GC请求时,所有进入到safepoint的Java线程会在一个Thread_Lock锁阻塞,直到当JVM操作完成后,VM释放Thread_Lock,阻塞的Java线程才能继续运行。
GC stop the world的时候,所有运行Java code的线程被阻塞,如果运行native code线程不去和Java代码交互,那么这些线程不需要阻塞。VM操作相关的线程也不会被阻塞。
safepoint只能处理正在运行的线程,它们可以主动运行到safepoint。而一些Sleep或者被blocked的线程不能主动运行到safepoint。这些线程也需要在GC的时候被标记检查,JVM引入了safe region的概念。safe region是指一块区域,这块区域中的引用都不会被修改,比如线程被阻塞了,那么它的线程堆栈中的引用是不会被修改的,JVM可以安全地进行标记。线程进入到safe region的时候先标识自己进入了safe region,等它被唤醒准备离开safe region的时候,先检查能否离开,如果GC已经完成,那么可以离开,否则就在safe region呆在。这可以理解,因为如果GC还没完成,那么这些在safe region中的线程也是被stop the world所影响的线程的一部分,如果让他们可以正常执行了,可能会影响标记的结果
可以设置JVM参数 -XX:+PrintSafepointStatistics –XX:PrintSafepointStatisticsCount=1 来输出safepoint的统计信息
参考资料:
找出栈上的指针/引用
What is a Java safepoint
GC safe-point (or safepoint) and safe-region
内存篇:JVM内存回收理论与实现
Safepoints in HotSpot JVM