调用过程和生命周期解析前言safepoint的调用过程和生命周期解析一、 Safepoint 的生命周期二、 核心源码剖析1. 触发与同步阶段SafepointSynchronize::begin()2. 线程如何感知并响应 Safepoint3. 恢复与结束阶段SafepointSynchronize::end()三、 典型调用栈分析1. VMThread 侧发起与等待栈2. JavaThread 侧JIT 编译代码触发 Page Fault 挂起栈四、 核心原理总结前言本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限文中内容难免存在疏漏恳请读者不吝指正。safepoint的调用过程和生命周期解析作为 Java 系统工程师理解Safepoint安全点的底层源码与生命周期是调优 JVM 性能、排查 STWStop-The-World大暂停的必修课。在 OpenJDK 8中Safepoint 的核心逻辑主要由SafepointSynchronize类位于share/vm/runtime/safepoint.cpp驱动。下面我们结合 OpenJDK 8源码深入拆解 Safepoint 的生命周期、核心调用栈以及关键源码实现。一、 Safepoint 的生命周期一个完整的 Safepoint 从触发到结束会经历以下 4 个核心阶段[运行状态] │ ▼ (1. 发起) VMThread 调用 begin()设置状态为 _synchronizing [同步阶段] ─── JavaThread 到达安全点轮询页置脏 / 解释器检测 │ ▼ (2. 达标) 所有 JavaThread 暂停状态变为 _synchronized [执行阶段] ─── 执行 GC、Biased Lock Revocation、Class Redefinition 等 STW 任务 │ ▼ (3. 恢复) VMThread 执行 end()重置状态为 _not_synchronized [唤醒阶段] ─── 恢复全局轮询页JavaThread 离开 Safepoint 继续执行二、 核心源码剖析1. 触发与同步阶段SafepointSynchronize::begin()当VMThread负责执行 JVM 内部安全任务的线程收到一个需要 STW 的请求时会调用begin()方法。这是拉开 Safepoint 序幕的核心函数。// share/vm/runtime/safepoint.cppvoidSafepointSynchronize::begin(){assert(Thread::current()-is_VM_thread(),Only VM thread can execute a safepoint);// 1. 更新全局安全点计数器_safepoint_counter;// 2. 将安全点状态从 _not_synchronized未同步设置为 _synchronizing正在同步_state_synchronizing;OrderAccess::fence();// 确保内存屏障让其他线程立即可见// 3. 关键将全局安全点轮询页面Polling Page设为不可读/不可写Mprotect 触发 SIGSEGV// 编译形JITed代码在运行时会定期读取这个页面一旦变脏立刻触发异常进入信号处理程序os::make_polling_page_unreadable();// 4. 等待所有运行中的 Java 线程进入安全点intiterations0;intactive_threads0;while(true){// 检查是否所有线程都已经到达安全点// 根据线程当前的状态如 _thread_in_native, _thread_in_Java来判断if(can_is_security_check_pass(active_threads)){break;// 所有线程已安全暂停跳出循环}// 如果耗时过长可能会触发 SafepointTimeout 警告if(SafepointTimeout!was_timeout(iterationsSafepointTimeoutDelay)){rc_with_timeout_mprotect();// 打印超时日志}// 适当让出 CPU 资源等待其他线程响应轮询os::naked_yield();}// 5. 此时所有 Java 线程均已暂停将状态修改为 _synchronized已同步完成_state_synchronized;OrderAccess::fence();}2. 线程如何感知并响应 SafepointJava 线程有不同的运行模式JVM 对其进入 Safepoint 的判定方式也不同解释执行Interpreter解释器在每条字节码执行前或向后跳转、方法返回时会检查一个全局的 Safepoint 地址。编译执行JIT CompiledJIT 编译器会在方法出口和循环回边Loop Backedge插入一句话test %eax, 0x160000读取轮询页。当begin()中将该页置脏时该指令触发硬件页错误捕获后转入SafepointSynchronize::handle_polling_page_exception()。本地代码Native Code正在执行 JNI 方法的线程无需暂停因为它们不会修改 Java 堆对象。但当它们准备从 JNI 返回 Java 空间时会检查状态若处于_synchronizing则会被阻塞。下面是线程从 JNI 返回或者在安全点前被阻塞的核心代码// share/vm/runtime/safepoint.cpp// 当 JavaThread 发现系统处于同步状态自愿调用此方法挂起自己voidSafepointSynchronize::block(JavaThread*thread){// 检查当前全局状态是否还是同步中/已同步if(_state!_not_synchronized){// 改变当前线程状态表明自己已经安全挂起// 这样 VMThread 就能在 can_is_security_check_pass() 中对本线程放行JavaThreadState saved_statethread-thread_state();thread-set_thread_state(_thread_blocked);// 进入等待队列挂起在 Safepoint_lock 锁上Safepoint_lock-lock_without_safepoint_check();// 循环等待直到状态恢复为 _not_synchronizedwhile(_state!_not_synchronized){Safepoint_lock-wait(Mutex::_no_safepoint_check_flag);}Safepoint_lock-unlock();// 被唤醒后恢复原先的线程状态继续执行 Java 代码thread-set_thread_state(saved_state);}}3. 恢复与结束阶段SafepointSynchronize::end()当 VMThread 顺利执行完 GC 或其他 STW 任务后会调用end()唤醒所有阻塞的 Java 线程。// share/vm/runtime/safepoint.cppvoidSafepointSynchronize::end(){assert(Thread::current()-is_VM_thread(),Only VM thread can execute a safepoint);// 1. 恢复全局轮询页面为可读// JIT 编译代码再度执行到轮询点时将恢复正常读取不再触发异常os::make_polling_page_readable();Safepoint_lock-lock_without_safepoint_check();// 2. 将全局状态重置为 _not_synchronized_state_not_synchronized;// 3. 关键通知并唤醒所有因为安全点被 block 在 Safepoint_lock 上的 JavaThreadSafepoint_lock-notify_all();Safepoint_lock-unlock();// 4. 清理并开启下一轮垃圾回收/安全点周期的准备工作RuntimeService::record_safepoint_end();}三、 典型调用栈分析当我们在做底层调优、或者由于 Safepoint 导致长暂停而观察jstack/pstack时最常见的底层 C 调用栈如下1. VMThread 侧发起与等待栈Thread 30517 (VM Thread): #0 pthread_cond_wait ... // 物理挂起等待某些顽固线程就位 #1 os::PlatformEvent::park() #2 Monitor::ILock(Thread*) #3 Monitor::lock_without_safepoint_check() #4 SafepointSynchronize::begin() -- 1. VMThread 触发安全点并进入同步循环 #5 VMThread::loop() -- 2. VMThread 核心轮询事件队列 #6 VMThread::run() -- 3. VM 线程启动2. JavaThread 侧JIT 编译代码触发 Page Fault 挂起栈Thread 30511 (Java Thread_1): #0 pthread_cond_wait ... // 线程在此处被真正挂起STW 发生中 #1 os::PlatformEvent::park() #2 Monitor::IWait(Thread*, long long) #3 Monitor::wait(bool, long, bool) #4 SafepointSynchronize::block(JavaThread*) -- 1. 线程在此处将自己修改为 _thread_blocked 并 wait #5 SafepointSynchronize::handle_polling_page_exception(JavaThread*) -- 2. 信号处理器捕获到 SIGSEGV交给 JVM 处理 #6 StubRoutines::_call_stub -- 3. 硬件层面的轮询指令 (test %eax, polling_page) 触发中断 #7 [Generated Method] -- 4. 正在跑的 JIT 编译业务代码如某个 controller 方法四、 核心原理总结硬件屏障巧妙利用 (mprotect)OpenJDK 并没有在 JIT 代码中加入繁琐的if (safepoint_flag)分支判断这会极大降低 CPU 流水线执行效率而是直接使用一行test汇编指令去读一个固定的内存页。正常时该页可读开销极小需要安全点时将其改为不可读利用 CPU 硬件层面的页错误Page Fault异步阻断执行流效率极高。状态机判定VMThread 不需要等所有线程都走到某一行代码只要 JavaThread 的状态处于_thread_in_native执行本地方法、_thread_in_vm、或者已被_thread_blocked阻塞VMThread 就认为该线程已经“安全”了可以开始 STW 任务。TTSP (Time To Safepoint) 优化如果业务代码中存在没有放置 Safepoint 的长循环例如通过Counted Loop优化的int循环Java 线程迟迟无法读取轮询页就会导致 TTSP 过长从而使整个 JVM 陷入长时间的伪挂起。这在生产调优中通常需要通过-XX:UseCountedLoopSafepoints来显式解决。