1. 信号处理与桩代码(Stub)
当线程访问安全点轮询页(Polling Page)时:
- 触发 SIGSEGV 信号:访问只读的轮询页会引发
SIGSEGV
异常。 - 信号处理函数:
pd_hotspot_signal_handler
检测到SafepointMechanism::is_poll_address
为真,调用SharedRuntime::get_poll_stub
获取桩代码入口地址(如polling_page_safepoint_handler_blob
)。 - 篡改 PC:
os::Posix::ucontext_set_pc(uc, stub)
将线程的 程序计数器(PC) 设置为桩代码地址。
2. 桩代码的职责
桩代码(如 polling_page_safepoint_handler_blob
)是平台相关的汇编代码,其核心逻辑为:
asm
复制
// 伪代码示例 call SafepointSynchronize::handle_polling_page_exception ; 调用安全点处理函数 ret
- 直接调用:桩代码通过
call
指令直接调用SafepointSynchronize::handle_polling_page_exception
。 - 触发阻塞:
handle_polling_page_exception
最终通过SafepointSynchronize::block
让线程阻塞在安全点。
3. 关键调用链
信号处理与安全点处理的完整路径:
信号处理函数 (javaSignalHandler)
→ PosixSignals::pd_hotspot_signal_handler
→ 检测到安全点轮询页(SafepointMechanism::is_poll_address)
→ SharedRuntime::get_poll_stub(pc) 获取桩代码地址
→ 篡改 PC 到桩代码(如 polling_page_safepoint_handler_blob)
→ 桩代码执行
→ SafepointSynchronize::handle_polling_page_exception
→ SafepointMechanism::process
→ SafepointSynchronize::block
→ 线程阻塞等待安全点
4. 核心设计思想
- 信号驱动:通过操作系统的内存保护机制(轮询页不可访问)触发信号,将控制权交给 JVM。
- 间接跳转:信号处理函数不直接调用安全点逻辑,而是通过修改线程执行路径(PC),跳转到桩代码。
- 桩代码桥接:桩代码作为 桥梁,将信号处理上下文与 JVM 内部安全点处理逻辑连接。
5. 普通线程阻塞的触发
- 所有 Java 线程:无论是用户线程、JIT 编译代码线程,还是解释器执行的线程,访问轮询页时都会触发此流程。
- 统一入口:无论线程原本在执行什么,最终都会通过桩代码调用
handle_polling_page_exception
,确保所有线程在安全点处阻塞。
总结
- 信号处理函数不直接调用:
handle_polling_page_exception
由 桩代码 直接调用,而非信号处理函数本身。 - 间接触发阻塞:通过篡改 PC 到桩代码,再由桩代码触发安全点处理逻辑,最终实现线程阻塞。
- 统一安全点处理:所有 Java 线程通过此机制在安全点同步,确保 GC 等操作的安全执行。
##源码
address SharedRuntime::get_poll_stub(address pc) {
address stub;
// Look up the code blob
CodeBlob *cb = CodeCache::find_blob(pc);
// Should be an nmethod
guarantee(cb != NULL && cb->is_compiled(), "safepoint polling: pc must refer to an nmethod");
// Look up the relocation information
assert(((CompiledMethod*)cb)->is_at_poll_or_poll_return(pc),
"safepoint polling: type must be poll");
#ifdef ASSERT
if (!((NativeInstruction*)pc)->is_safepoint_poll()) {
tty->print_cr("bad pc: " PTR_FORMAT, p2i(pc));
Disassembler::decode(cb);
fatal("Only polling locations are used for safepoint");
}
#endif
bool at_poll_return = ((CompiledMethod*)cb)->is_at_poll_return(pc);
bool has_wide_vectors = ((CompiledMethod*)cb)->has_wide_vectors();
if (at_poll_return) {
assert(SharedRuntime::polling_page_return_handler_blob() != NULL,
"polling page return stub not created yet");
stub = SharedRuntime::polling_page_return_handler_blob()->entry_point();
} else if (has_wide_vectors) {
assert(SharedRuntime::polling_page_vectors_safepoint_handler_blob() != NULL,
"polling page vectors safepoint stub not created yet");
stub = SharedRuntime::polling_page_vectors_safepoint_handler_blob()->entry_point();
} else {
assert(SharedRuntime::polling_page_safepoint_handler_blob() != NULL,
"polling page safepoint stub not created yet");
stub = SharedRuntime::polling_page_safepoint_handler_blob()->entry_point();
}
log_debug(safepoint)("... found polling page %s exception at pc = "
INTPTR_FORMAT ", stub =" INTPTR_FORMAT,
at_poll_return ? "return" : "loop",
(intptr_t)pc, (intptr_t)stub);
return stub;
}
bool PosixSignals::pd_hotspot_signal_handler(int sig, siginfo_t* info,
ucontext_t* uc, JavaThread* thread) {
if (sig == SIGILL &&
((info->si_addr == (caddr_t)check_simd_fault_instr)
|| info->si_addr == (caddr_t)check_vfp_fault_instr
|| info->si_addr == (caddr_t)check_vfp3_32_fault_instr
|| info->si_addr == (caddr_t)check_mp_ext_fault_instr)) {
// skip faulty instruction + instruction that sets return value to
// success and set return value to failure.
os::Posix::ucontext_set_pc(uc, (address)info->si_addr + 8);
uc->uc_mcontext.arm_r0 = 0;
return true;
}
address stub = NULL;
address pc = NULL;
bool unsafe_access = false;
if (info != NULL && uc != NULL && thread != NULL) {
pc = (address) os::Posix::ucontext_get_pc(uc);
// Handle ALL stack overflow variations here
if (sig == SIGSEGV) {
address addr = (address) info->si_addr;
// check if fault address is within thread stack
if (thread->is_in_full_stack(addr)) {
// stack overflow
StackOverflow* overflow_state = thread->stack_overflow_state();
if (overflow_state->in_stack_yellow_reserved_zone(addr)) {
overflow_state->disable_stack_yellow_reserved_zone();
if (thread->thread_state() == _thread_in_Java) {
// Throw a stack overflow exception. Guard pages will be reenabled
// while unwinding the stack.
stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::STACK_OVERFLOW);
} else {
// Thread was in the vm or native code. Return and try to finish.
return true;
}
} else if (overflow_state->in_stack_red_zone(addr)) {
// Fatal red zone violation. Disable the guard pages and fall through
// to handle_unexpected_exception way down below.
overflow_state->disable_stack_red_zone();
tty->print_raw_cr("An irrecoverable stack overflow has occurred.");
} else {
// Accessing stack address below sp may cause SEGV if current
// thread has MAP_GROWSDOWN stack. This should only happen when
// current thread was created by user code with MAP_GROWSDOWN flag
// and then attached to VM. See notes in os_linux.cpp.
if (thread->osthread()->expanding_stack() == 0) {
thread->osthread()->set_expanding_stack();
if (os::Linux::manually_expand_stack(thread, addr)) {
thread->osthread()->clear_expanding_stack();
return true;
}
thread->osthread()->clear_expanding_stack();
} else {
fatal("recursive segv. expanding stack.");
}
}
}
}
if (thread->thread_state() == _thread_in_Java) {
// Java thread running in Java code => find exception handler if any
// a fault inside compiled code, the interpreter, or a stub
if (sig == SIGSEGV && SafepointMechanism::is_poll_address((address)info->si_addr)) {
stub = SharedRuntime::get_poll_stub(pc);
} else if (sig == SIGBUS) {
// BugId 4454115: A read from a MappedByteBuffer can fault
// here if the underlying file has been truncated.
// Do not crash the VM in such a case.
CodeBlob* cb = CodeCache::find_blob_unsafe(pc);
CompiledMethod* nm = (cb != NULL) ? cb->as_compiled_method_or_null() : NULL;
if ((nm != NULL && nm->has_unsafe_access()) || (thread->doing_unsafe_access() && UnsafeCopyMemory::contains_pc(pc))) {
unsafe_access = true;
}
} else if (sig == SIGSEGV &&
MacroAssembler::uses_implicit_null_check(info->si_addr)) {
// Determination of interpreter/vtable stub/compiled code null exception
CodeBlob* cb = CodeCache::find_blob_unsafe(pc);
if (cb != NULL) {
stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::IMPLICIT_NULL);
}
} else if (sig == SIGILL && *(int *)pc == NativeInstruction::zombie_illegal_instruction) {
// Zombie
stub = SharedRuntime::get_handle_wrong_method_stub();
}
} else if ((thread->thread_state() == _thread_in_vm ||
thread->thread_state() == _thread_in_native) &&
sig == SIGBUS && thread->doing_unsafe_access()) {
unsafe_access = true;
}
// jni_fast_Get<Primitive>Field can trap at certain pc's if a GC kicks in
// and the heap gets shrunk before the field access.
if (sig == SIGSEGV || sig == SIGBUS) {
address addr = JNI_FastGetField::find_slowcase_pc(pc);
if (addr != (address)-1) {
stub = addr;
}
}
}
if (unsafe_access && stub == NULL) {
// it can be an unsafe access and we haven't found
// any other suitable exception reason,
// so assume it is an unsafe access.
address next_pc = pc + Assembler::InstructionSize;
if (UnsafeCopyMemory::contains_pc(pc)) {
next_pc = UnsafeCopyMemory::page_error_continue_pc(pc);
}
#ifdef __thumb__
if (uc->uc_mcontext.arm_cpsr & PSR_T_BIT) {
next_pc = (address)((intptr_t)next_pc | 0x1);
}
#endif
stub = SharedRuntime::handle_unsafe_access(thread, next_pc);
}
if (stub != NULL) {
#ifdef __thumb__
if (uc->uc_mcontext.arm_cpsr & PSR_T_BIT) {
intptr_t p = (intptr_t)pc | 0x1;
pc = (address)p;
// Clear Thumb mode bit if we're redirected into the ARM ISA based code
if (((intptr_t)stub & 0x1) == 0) {
uc->uc_mcontext.arm_cpsr &= ~PSR_T_BIT;
}
} else {
// No Thumb2 compiled stubs are triggered from ARM ISA compiled JIT'd code today.
// The support needs to be added if that changes
assert((((intptr_t)stub & 0x1) == 0), "can't return to Thumb code");
}
#endif
// save all thread context in case we need to restore it
if (thread != NULL) thread->set_saved_exception_pc(pc);
os::Posix::ucontext_set_pc(uc, stub);
return true;
}
return false;
}