目录
2、os::serialize_thread_states / write_memory_serialize_page
3、TemplateInterpreter::notice_safepoints / ignore_safepoints
4、os::make_polling_page_unreadable / make_polling_page_readable
5、JavaThread::check_safepoint_and_suspend_for_native_trans
本篇博客主要讲解GC的核心概念之安全点SafepointSynchronize的实现细节。
1、定义
SafepointSynchronize的定义位于hotspot\src\share\vm\runtime\safepoint.hpp中,用来实现安全点的进入和退出,其包含的属性如下:
- static volatile SynchronizeState _state; // 表示安全点的状态
- static volatile int _waiting_to_block; // 等待被阻塞(同步)的线程数
- static int _current_jni_active_count; //记录安全点期间处于JNI关键区的线程的总数
- static volatile int _safepoint_counter; //进入和退出安全点的总次数,进入和退出时都会加1
- static long _end_of_last_safepoint; //上一次退出安全点的时间
- static jlong _safepoint_begin_time; // 进入安全点的时间,单位是纳秒
- static SafepointStats* _safepoint_stats; // SafepointStats数组,剩下几个参数都是跟SafepointStats配合使用,用来统计安全点相关的数据
- static int _cur_stat_index; // current index to the above array
- static julong _safepoint_reasons[]; // safepoint count for each VM op
- static julong _coalesced_vmop_count; // coalesced vmop count
- static jlong _max_sync_time; // maximum sync time in nanos
- static jlong _max_vmop_time; // maximum vm operation time in nanos
- static float _ts_of_current_safepoint; // 进入安全点的时间,单位是秒
其中SynchronizeState是一个枚举,用来描述安全点的状态,其定义如下:
其中0表示所有线程都不在安全点上,1表示所有线程在执行安全点同步中,2表示所有线程都停留在安全点上,只有VMThread在执行。
SafepointStats是一个数据结构,用来记录触发本次安全点的相关信息,其定义如下:
线程处于不同的状态下都有对应的机制让该线程进入到安全点,主要有以下几种:
- 处于解释执行中,解释器通过字节码路由表(dispatch table)开始执行下一个字节码时会检查安全点的状态
- 处于本地代码执行中,即正在执行JNI方法,当Java线程从JNI方法中退出后必须检查安全点的状态,VMThread并不会等待处于本地代码执行中的线程进入阻塞状态。
- 处于编译执行中,编译代码在适当的位置上会读取Safepoint Polling内存页,如果该内存页是脏的,则表示需要进入安全点
- 处于JVM执行中,即JVM执行上述三种状态的切换时,JVM会在切换前检查安全点的状态
- 处于阻塞状态,即等待某个锁,JVM会一直阻塞该线程直到VMThead从安全点退出
线程的状态有一个对应的枚举JavaThreadState,其定义如下,其中奇数的都是中间状态,短时间内存在,偶数的是
2、os::serialize_thread_states / write_memory_serialize_page
该方法相当于执行OrderAccess::fence()方法,会强制各线程对线程状态的修改从高速缓存同步到内存中,从而方便SafepointSynchronize能够高效准确的获取线程状态,其实现如下:
void os::serialize_thread_states() {
//SerializePageLock用于同步对_mem_serialize_page的写入操作,获取锁
Thread::muxAcquire(&SerializePageLock, "serialize_thread_states");
//将_mem_serialize_page对应的内存页先设置为只读,再设置为可读写,底层调用Linux的mprotect方法
//Linux修改对应内存页的属性时会强制高速缓存中对该内存页的修改刷新到内存中
os::protect_memory((char *)os::get_memory_serialize_page(),
os::vm_page_size(), MEM_PROT_READ);
os::protect_memory((char *)os::get_memory_serialize_page(),
os::vm_page_size(), MEM_PROT_RW);
//释放锁
Thread::muxRelease(&SerializePageLock);
}
static address get_memory_serialize_page() {
return (address)_mem_serialize_page;
}
int os::vm_page_size() {
assert(os::Linux::page_size() != -1, "must call os::init");
return os::Linux::page_size();
}
_mem_serialize_page是通过set_memory_serialize_page方法初始化的,该方法的实现如下:
void os::set_memory_serialize_page(address page) {
int count = log2_intptr(sizeof(class JavaThread)) - log2_int(64);
_mem_serialize_page = (volatile int32_t *)page;
//SerializePageShiftCount是一个常量,64位下取值为4
assert(SerializePageShiftCount == count,
"thread size changed, fix SerializePageShiftCount constant");
//vm_page_size返回内存页的大小,64位下默认是4k,4096字节
//sizeof(int32_t)是32位int的大小,是4字节
set_serialize_page_mask((uintptr_t)(vm_page_size() - sizeof(int32_t)));
}
static void set_serialize_page_mask(uintptr_t mask) {
_serialize_page_mask = mask;
}
_mem_serialize_page怎么跟线程状态有关联了?答案在write_memory_serialize_page,该方法的实现如下:
static inline void write_memory_serialize_page(JavaThread *thread) {
uintptr_t page_offset = ((uintptr_t)thread >>
get_serialize_page_shift_count()) &
get_serialize_page_mask();
//将指定位置的4字节数据修改为1,即最多支持1024个线程
*(volatile int32_t *)((uintptr_t)_mem_serialize_page+page_offset) = 1;
}
static int get_serialize_page_shift_count() {
//SerializePageShiftCount是一个常量,值为4
return SerializePageShiftCount;
}
该方法主要是线程状态流转时会调用,其调用链:
当执行os::serialize_thread_states期间,如果有其他线程并行的执行write_memory_serialize_page方法且_mem_serialize_page是只读的,则执行write_memory_serialize_page方法的Linux内核线程会终止对应的用户线程的执行,然后向该线程发送SIGSEGV(表示操作指定的内存段错误)信号量,JVM负责处理信号的线程接受到该信号后,判断触发SIGSEGV的内存地址位于_mem_serialize_page中,则会调用os::block_on_serialize_page_trap方法阻塞该用户线程,直到os::serialize_thread_states执行完成,然后重新执行修改,block_on_serialize_page_trap的实现如下:
void os::block_on_serialize_page_trap() {
if (TraceSafepoint) {
tty->print_cr("Block until the serialize page permission restored");
}
//尝试去获取锁,会阻塞当前线程直到获取成功,获取成功表示serialize_thread_states方法执行完成
Thread::muxAcquire(&SerializePageLock, "set_memory_serialize_page");
//再次释放该锁
Thread::muxRelease(&SerializePageLock);
}
static bool is_memory_serialize_page(JavaThread *thread, address addr) {
if (UseMembar) return false;
if (thread == NULL) return false;
//判断addr是否在_mem_serialize_page中
address page = (address) _mem_serialize_page;
return addr >= page