OpenJDK 17 安全点检查机制深入解析
什么是安全点?
安全点(Safepoint)是 Java 虚拟机中的一个重要概念,它是程序执行过程中的特殊位置,当所有线程都到达安全点时,JVM 才能执行一些需要暂停所有线程的操作,如垃圾回收、代码反优化、线程堆栈遍历等。
解释器中的安全点检查
在 OpenJDK 17 的解释器实现中,安全点检查是通过 InterpreterRuntime::at_safepoint
函数处理的:
cpp
JRT_ENTRY(void, InterpreterRuntime::at_safepoint(JavaThread* current))if (JvmtiExport::should_post_single_step()) {StackWatermarkSet::before_unwind(current);LastFrameAccessor last_frame(current);JvmtiExport::at_single_stepping_point(current, last_frame.method(), last_frame.bcp());} JRT_END
这个函数主要处理 JVMTI 的单步调试事件。当启用单步调试时,它会在安全点执行必要的操作,包括处理可能被展开的帧。
线程状态转换与安全点
线程在执行过程中需要频繁地在 Java 代码、本地代码和虚拟机代码之间切换,这些转换点都是潜在的安全点检查位置:
cpp
class ThreadInVMfromJava : public ThreadStateTransition { public:ThreadInVMfromJava(JavaThread* thread, bool check_asyncs = true) : ThreadStateTransition(thread), _check_asyncs(check_asyncs) {trans_from_java(_thread_in_vm);}~ThreadInVMfromJava() {// ... 清理工作trans(_thread_in_vm, _thread_in_Java);if (_thread->has_special_runtime_exit_condition()) _thread->handle_special_runtime_exit_condition(_check_asyncs);} };
ThreadStateTransition
类管理线程状态转换,确保在转换过程中正确处理安全点。
安全点检查的核心机制
安全点检查的核心实现在 transition
函数中:
cpp
static inline void transition(JavaThread *thread, JavaThreadState from, JavaThreadState to) {assert(thread->thread_state() == from, "coming from wrong thread state");// 检查安全点验证器并清理未处理的 oopsthread->check_possible_safepoint();// 切换到过渡状态确保 VM 线程能看到thread->set_thread_state_fence((JavaThreadState)(from + 1));// 处理安全点请求SafepointMechanism::process_if_requested(thread);thread->set_thread_state(to); }
这个过程包含几个关键步骤:
验证当前线程状态
检查可能的安全点
设置过渡状态确保可见性
处理安全点请求
设置最终状态
安全点处理流程
SafepointMechanism::process_if_requested
是安全点处理的核心:
cpp
void SafepointMechanism::process_if_requested(JavaThread* thread, bool allow_suspend) {if (local_poll_armed(thread)) {process(thread, allow_suspend);} }
如果本地轮询标志被设置,则进入 process
方法:
cpp
void SafepointMechanism::process(JavaThread *thread, bool allow_suspend) {do {if (global_poll()) {OrderAccess::loadload();SafepointSynchronize::block(thread); // 阻塞线程}StackWatermarkSet::on_safepoint(thread);need_rechecking = thread->handshake_state()->has_operation() && thread->handshake_state()->process_by_self(allow_suspend);} while (need_rechecking);update_poll_values(thread);OrderAccess::cross_modify_fence(); }
线程阻塞机制
当检测到全局安全点请求时,线程会调用 SafepointSynchronize::block
进入阻塞状态:
cpp
void SafepointSynchronize::block(JavaThread *thread) {// 确保线程有效且未终止if (thread->is_terminated()) {thread->block_if_vm_exited();return;}JavaThreadState state = thread->thread_state();thread->frame_anchor()->make_walkable(thread);uint64_t safepoint_id = SafepointSynchronize::safepoint_counter();switch(state) {case _thread_in_vm_trans:case _thread_in_Java:case _thread_in_native_trans:// ... 其他状态thread->safepoint_state()->set_safepoint_id(safepoint_id);thread->set_thread_state_fence(_thread_blocked);_wait_barrier->wait(static_cast<int>(safepoint_id)); // 等待屏障thread->set_thread_state(state);thread->safepoint_state()->reset_safepoint_id();break;default:fatal("Illegal threadstate encountered: %d", state);} }
解释器中的安全点入口
解释器为每种类型栈状态(TosState)生成安全点入口:
cpp
Interpreter::_safept_entry =EntryPoint(generate_safept_entry_for(atos, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)),generate_safept_entry_for(itos, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)),// ... 其他类型);
这些入口点被设置到安全点表中:
cpp
void TemplateInterpreterGenerator::set_safepoints_for_all_bytes() {for (int i = 0; i < DispatchTable::length; i++) {Bytecodes::Code code = (Bytecodes::Code)i;if (Bytecodes::is_defined(code)) Interpreter::_safept_table.set_entry(code, Interpreter::_safept_entry);} }
汇编层面的安全点检查
在解释器分发指令时,会进行安全点检查:
cpp
void InterpreterMacroAssembler::dispatch_base(TosState state,address* table,bool verifyoop,bool generate_poll) {// ... 验证和准备工作address* const safepoint_table = Interpreter::safept_table(state);#ifdef _LP64if (table != safepoint_table && generate_poll) {testb(Address(r15_thread, JavaThread::polling_word_offset()), SafepointMechanism::poll_bit());jccb(Assembler::zero, no_safepoint);lea(rscratch1, ExternalAddress((address)safepoint_table));jmpb(dispatch);} #endif }
这段汇编代码检查线程本地的轮询字,如果设置了安全点标志,则跳转到安全点处理表。
总结
OpenJDK 17 的安全点机制是一个复杂但高效的系统,它通过以下方式工作:
状态转换检查:在线程状态转换时检查安全点
本地轮询:每个线程有一个本地轮询字,定期检查
全局安全点:当需要所有线程暂停时,设置全局安全点标志
屏障等待:线程在安全点屏障处等待,直到安全点操作完成
恢复执行:安全点操作完成后,所有线程恢复执行
这种设计确保了 JVM 能够在需要时高效地暂停所有线程,同时最小化对正常执行的影响。安全点机制的实现涉及解释器、运行时系统、线程管理和底层汇编代码的紧密协作,是 JVM 实现的关键组件之一。
##源码
JRT_ENTRY(void, InterpreterRuntime::at_safepoint(JavaThread* current))// We used to need an explict preserve_arguments here for invoke bytecodes. However,// stack traversal automatically takes care of preserving arguments for invoke, so// this is no longer needed.// JRT_END does an implicit safepoint check, hence we are guaranteed to block// if this is called during a safepointif (JvmtiExport::should_post_single_step()) {// This function is called by the interpreter when single stepping. Such single// stepping could unwind a frame. Then, it is important that we process any frames// that we might return into.StackWatermarkSet::before_unwind(current);// We are called during regular safepoints and when the VM is// single stepping. If any thread is marked for single stepping,// then we may have JVMTI work to do.LastFrameAccessor last_frame(current);JvmtiExport::at_single_stepping_point(current, last_frame.method(), last_frame.bcp());}
JRT_ENDclass ThreadInVMfromJava : public ThreadStateTransition {bool _check_asyncs;public:ThreadInVMfromJava(JavaThread* thread, bool check_asyncs = true) : ThreadStateTransition(thread), _check_asyncs(check_asyncs) {trans_from_java(_thread_in_vm);}~ThreadInVMfromJava() {if (_thread->stack_overflow_state()->stack_yellow_reserved_zone_disabled()) {_thread->stack_overflow_state()->enable_stack_yellow_reserved_zone();}trans(_thread_in_vm, _thread_in_Java);// We prevent asynchronous exceptions from being installed on return to Java in situations// where we can't tolerate them. See bugs: 4324348, 4854693, 4998314, 5040492, 5050705.if (_thread->has_special_runtime_exit_condition()) _thread->handle_special_runtime_exit_condition(_check_asyncs);}
};void trans(JavaThreadState from, JavaThreadState to) { transition(_thread, from, to); }// Change threadstate in a manner, so safepoint can detect changes.// Time-critical: called on exit from every runtime routinestatic inline void transition(JavaThread *thread, JavaThreadState from, JavaThreadState to) {assert(from != _thread_in_Java, "use transition_from_java");assert(from != _thread_in_native, "use transition_from_native");assert((from & 1) == 0 && (to & 1) == 0, "odd numbers are transitions states");assert(thread->thread_state() == from, "coming from wrong thread state");// Check NoSafepointVerifier// This also clears unhandled oops if CheckUnhandledOops is used.thread->check_possible_safepoint();// Change to transition state and ensure it is seen by the VM thread.thread->set_thread_state_fence((JavaThreadState)(from + 1));SafepointMechanism::process_if_requested(thread);thread->set_thread_state(to);}void SafepointMechanism::process_if_requested(JavaThread* thread, bool allow_suspend) {// Macos/aarch64 should be in the right state for safepoint (e.g.// deoptimization needs WXWrite). Crashes caused by the wrong state rarely// happens in practice, making such issues hard to find and reproduce.
#if defined(ASSERT) && defined(__APPLE__) && defined(AARCH64)if (AssertWXAtThreadSync) {thread->assert_wx_state(WXWrite);}
#endifif (local_poll_armed(thread)) {process(thread, allow_suspend);}
}void SafepointMechanism::process(JavaThread *thread, bool allow_suspend) {// Read global poll and has_handshake after local pollOrderAccess::loadload();// local poll already checked, if used.bool need_rechecking;do {if (global_poll()) {// Any load in ::block() must not pass the global poll load.// Otherwise we might load an old safepoint counter (for example).OrderAccess::loadload();//yym 注释安全点线程阻塞SafepointSynchronize::block(thread);}// The call to on_safepoint fixes the thread's oops and the first few frames.//// The call has been carefully placed here to cater to a few situations:// 1) After we exit from block after a global poll// 2) After a thread races with the disarming of the global poll and transitions from native/blocked// 3) Before the handshake code is runStackWatermarkSet::on_safepoint(thread);need_rechecking = thread->handshake_state()->has_operation() && thread->handshake_state()->process_by_self(allow_suspend);} while (need_rechecking);update_poll_values(thread);OrderAccess::cross_modify_fence();
}// -------------------------------------------------------------------------------------------------------
// Implementation of Safepoint blocking pointvoid SafepointSynchronize::block(JavaThread *thread) {//yym-gaizao// 获取线程名字并打印oop thread_obj = thread->threadObj();const char* thread_name = "UNKNOWN";char buffer[256]; // 增加缓冲区用于组合字符串if (thread_obj != nullptr) {oop name_oop = java_lang_Thread::name(thread_obj);if (name_oop != nullptr) {thread_name = java_lang_String::as_utf8_string(name_oop);}}// 组合字符串并打印int len = snprintf(buffer, sizeof(buffer), "----SafepointSynchronize::block----Thread %s \n", thread_name);write(STDERR_FILENO, buffer, len);// tty->print_cr("@@@@yym%%%%----SafepointSynchronize::block----Thread: %s", thread_name);//信号函数处理不安全函数 异常崩溃assert(thread != NULL, "thread must be set");// Threads shouldn't block if they are in the middle of printing, but...ttyLocker::break_tty_lock_for_safepoint(os::current_thread_id());// Only bail from the block() call if the thread is gone from the// thread list; starting to exit should still block.if (thread->is_terminated()) {// block current thread if we come here from native code when VM is gonethread->block_if_vm_exited();// otherwise do nothingreturn;}JavaThreadState state = thread->thread_state();thread->frame_anchor()->make_walkable(thread);uint64_t safepoint_id = SafepointSynchronize::safepoint_counter();// Check that we have a valid thread_state at this pointswitch(state) {case _thread_in_vm_trans:case _thread_in_Java: // From compiled codecase _thread_in_native_trans:case _thread_blocked_trans:case _thread_new_trans:// We have no idea where the VMThread is, it might even be at next safepoint.// So we can miss this poll, but stop at next.// Load dependent store, it must not pass loading of safepoint_id.thread->safepoint_state()->set_safepoint_id(safepoint_id); // Release store// This part we can skip if we notice we miss or are in a future safepoint.OrderAccess::storestore();// Load in wait barrier should not float upthread->set_thread_state_fence(_thread_blocked);_wait_barrier->wait(static_cast<int>(safepoint_id));assert(_state != _synchronized, "Can't be");// If barrier is disarmed stop store from floating above loads in barrier.OrderAccess::loadstore();thread->set_thread_state(state);// Then we reset the safepoint id to inactive.thread->safepoint_state()->reset_safepoint_id(); // Release storeOrderAccess::fence();break;default:fatal("Illegal threadstate encountered: %d", state);}guarantee(thread->safepoint_state()->get_safepoint_id() == InactiveSafepointCounter,"The safepoint id should be set only in block path");// cross_modify_fence is done by SafepointMechanism::process_if_requested// which is the only caller here.
}{ CodeletMark cm(_masm, "safepoint entry points");Interpreter::_safept_entry =EntryPoint(generate_safept_entry_for(atos, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)),generate_safept_entry_for(itos, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)),generate_safept_entry_for(ltos, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)),generate_safept_entry_for(ftos, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)),generate_safept_entry_for(dtos, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)),generate_safept_entry_for(vtos, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)));}void TemplateInterpreterGenerator::set_safepoints_for_all_bytes() {for (int i = 0; i < DispatchTable::length; i++) {Bytecodes::Code code = (Bytecodes::Code)i;if (Bytecodes::is_defined(code)) Interpreter::_safept_table.set_entry(code, Interpreter::_safept_entry);}
}static address* safept_table(TosState state) { return _safept_table.table_for(state); }void InterpreterMacroAssembler::dispatch_base(TosState state,address* table,bool verifyoop,bool generate_poll) {verify_FPU(1, state);if (VerifyActivationFrameSize) {Label L;mov(rcx, rbp);subptr(rcx, rsp);int32_t min_frame_size =(frame::link_offset - frame::interpreter_frame_initial_sp_offset) *wordSize;cmpptr(rcx, (int32_t)min_frame_size);jcc(Assembler::greaterEqual, L);stop("broken stack frame");bind(L);}if (verifyoop) {interp_verify_oop(rax, state);}address* const safepoint_table = Interpreter::safept_table(state);
#ifdef _LP64Label no_safepoint, dispatch;if (table != safepoint_table && generate_poll) {NOT_PRODUCT(block_comment("Thread-local Safepoint poll"));testb(Address(r15_thread, JavaThread::polling_word_offset()), SafepointMechanism::poll_bit());jccb(Assembler::zero, no_safepoint);lea(rscratch1, ExternalAddress((address)safepoint_table));jmpb(dispatch);}bind(no_safepoint);lea(rscratch1, ExternalAddress((address)table));bind(dispatch);jmp(Address(rscratch1, rbx, Address::times_8));#elseAddress index(noreg, rbx, Address::times_ptr);if (table != safepoint_table && generate_poll) {NOT_PRODUCT(block_comment("Thread-local Safepoint poll"));Label no_safepoint;const Register thread = rcx;get_thread(thread);testb(Address(thread, JavaThread::polling_word_offset()), SafepointMechanism::poll_bit());jccb(Assembler::zero, no_safepoint);ArrayAddress dispatch_addr(ExternalAddress((address)safepoint_table), index);jump(dispatch_addr);bind(no_safepoint);}{ArrayAddress dispatch_addr(ExternalAddress((address)table), index);jump(dispatch_addr);}
#endif // _LP64
}