SafePoint
简介
A point in program where the state of execution is known by the VM,即代码中VM能够准确知道执行状态的位置。
safepoint可以用在不同地方,比如GC、Deoptimization,在HotspotVM中,GC safepoint比较常见,需要一个数据结构记录每个线程的调用栈、寄存器等一些重要的数据区域里什么地方包含了GC管理的指针。
从线程角度看,safepoint可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,如果有需要,可以在这个位置暂停,比如发生GC时,需要暂停所有活动线程,但是该线程在这个时刻,还没有执行到一个安全点,所以该线程应该继续执行,到达下一个安全点的时候暂停,然后才开始GC,该线程等待GC结束。
安全点
根节点枚举
HotSpot虚拟机使用可达性分析算法
确定对象是否可以被GC。
可达性分析算法从一系列GCRoot对象开始,向下搜索引用链,如果一个对象没有与任何GCRoot对象关联,这个对象就会被判定为可回收对象。过程称之为根节点枚举,也就是垃圾回收中的标记过程。
GCRoot包括以下对象:
- 虚拟机栈上的本地变量表引用的对象;
- 方法区中类的静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI引用的对象;
SafePoint是程序中的某些位置,线程执行到这些位置时,线程中的某些状态是确定的,在safePoint可以记录OopMap信息,线程在safePoint停顿,虚拟机进行GC。一个线程可以在SafePoint上,也可以不在SafePoint上。一个线程在SafePoint时,它的状态可以安全地被其他JVM线程所操作和观测。
线程停顿方式有两种,抢先式中断和主动式中断:
- 抢先式中断:虚拟机需要GC时,中断所有线程,让没有到达SafePoint的线程继续执行至SafePoint并中断;
- 主动式中断:虚拟机不直接中断线程,而是在内存中设置标志位,线程检查到标志位被设置,运行至SafePoint时主动中断;
hotspot采用的是主动检测的方式. 而主动检测的方式中又分为两种方式:
- 指定点执行检测代码;
- polling page访问异常触发;
hotspot会根据运行时的信息来统计, 并将高频率执行的java字节码直接翻译成本地代码, 由此提高执行效率. 因此, hotspot有两种执行方式, 一个是解释执行, 一个是编译执行。指定点检测主要是解释执行用的,对于需要高效实现的地方,则采用polling page。
polling page和普通物理页面没什么区别,需要safepoint时, 会修改该页面的权限为不可访问, 这样编译的代码在访问这个页面时, 会触发段违规异常(SEGEV). 而hotspot在启动时捕获了这个异常, 当意识到是访问polling page导致时, 则主动挂起。
SafePoint一般出现在以下位置:
- 循环体的结尾;
- 方法返回前;
- 调用方法的call之后;
- 抛出异常的位置;
线程挂起
如果触发GC动作,VM thread会在VMThread::loop()
方法中调用SafepointSynchronize::begin()
方法,最终使所有的线程都进入到safepoint。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void VMThread::loop() {
if (timedout && (SafepointALot ||
SafepointSynchronize::is_cleanup_needed())) {
MutexUnlockerEx mul(VMOperationQueue_lock,
Mutex::_no_safepoint_check_flag);
// Force a safepoint since we have not had one for at least
// 'GuaranteedSafepointInterval' milliseconds. This will run all
// the clean-up processing that needs to be done regularly at a
// safepoint
SafepointSynchronize::begin();
#ifdef ASSERT
if (GCALotAtAllSafepoints) InterfaceSupport::check_gc_alot();
#endif
SafepointSynchronize::end();
}
}
### 线程有五种不同的状态对应五种挂起的措施
-
执行Java code
在执行字节码时会检查safepoint状态,因为在begin方法中会调用Interpreter::notice_safepoints()方法,通知解释器更新dispatch table
-
执行native code
如果VM thread发现一个Java thread正在执行native code,并不会等待该Java thread阻塞,不过当该Java thread从native code返回时,必须检查safepoint状态,看是否需要进行阻塞。
-
执行complied code
如果想进入safepoint,则设置polling page不可读,当Java thread发现该内存页不可读时,最终会被阻塞挂起。在
SafepointSynchronize::begin()
方法中,通过os::make_polling_page_unreadable()
方法设置polling page为不可读。 -
线程处于Block状态
即使线程已经满足了block condition,也要等到safepoint operation完成,如GC操作,才能返回。
-
线程正在转换状态
会去检查safepoint状态,如果需要阻塞,就把自己挂起。
线程恢复
有了begin方法,自然有对应的end方法,在SafepointSynchronize::end()中,会最终唤醒所有挂起等待的线程,大概实现如下:
- 重新设置pooling page为可读;
- 设置解释器为ignore_safepoints;
- 唤醒所有挂起等待的线程;
对JVM的影像
通过设置JVM参数 -XX:+PrintGCApplicationStoppedTime
, 可以打出系统停止的时间,大概如下:
安全区域(SafeRegion)
安全点机制保证了程序执行的时候,在不太长的时间就会遇到可进入gc的安全点。但是如果线程处于sleep状态或者blocked状态的时候,这时线程无法响应jvm的中断请求,就需要安全区域。
安全区域是指在一段代码片段中,引用关系不会发生变化,在该区域的任何地方发生gc都是安全的。
- 线程进入SafeRegion会给自己加标记,告诉虚拟机可以进行GC;
- 线程准备离开SafeRegion前会询问虚拟机GC是否完成。