+---------------------+ +-----------------------------------+
| External Process | | Java Process running on HotSpot |
| sending SIGSEGV | ------> | JVM |
| (kill -11) | | Likely JVM Crash or Core Dump |
+---------------------+ +-----------------------------------+
字符串 JVM是否总是能够检测外部SIGSEGV是否是外部SIGSEGV,或者当外部SIGSEGV在特定时间发生时(即当预期潜在的空访问时),是否可能将其混淆为空访问? 同样,它不应该这样做,但这是JVM行为的一个特定于实现的方面。 这意味着在实践中发生这种混淆的可能性可能会因JVM版本、正在执行的特定代码以及信号发出时JVM的状态而异。 例如,参见“How does the JVM know when to throw a NullPointerException“ JVM可以使用虚拟内存硬件来实现空值检查,JVM将其虚拟地址空间中的页面零Map到不可读+不可写的页面。 由于null被表示为零,当Java代码试图解引用null时,这将试图访问一个不可寻址的页面,并将导致OS向JVM发送“segfault”信号。 JVM的segfault信号处理程序可以捕获它,找出代码在哪里执行,并在适当线程的堆栈上创建和抛出NPE。 在这种情况下,应该很容易区分来自代码执行中的捕获信号和来自OS的接收信号。 别名:Can a SIGSEGV in Java not crash the JVM? 在某些情况下,JVM的SIGSEGV信号处理程序可能会将SIGSEGV事件转换为Java异常。 只有在JVM硬崩溃无法发生的情况下,才会发生这种情况;例如,当事件发生时,触发SIGSEGV的线程正在本机库中执行代码。 例如: HotSpot JVM故意在启动时生成SIGSEGV来检查某些CPU功能。没有开关可以关闭它。我建议完全跳过gdb中的SIGSEGV,因为JVM在许多情况下会将其用于自己的目的。 如果SIGSEGV在外部触发时堆栈恰好位于访问地址处,该怎么办? hotspot在JDK-8255711中的信号处理方面进行了重大重构,导致了commit dd8e4ff。 当前代码为os_linux_x86.cpp#PosixSignals::pd_hotspot_signal_handler
// decide if this trap can be handled by a stub
address stub = nullptr;
address pc = nullptr;
//%note os_trap_1
if (info != nullptr && uc != nullptr && thread != nullptr) {
pc = (address) os::Posix::ucontext_get_pc(uc);
if (sig == SIGSEGV && info->si_addr == 0 && info->si_code == SI_KERNEL) {
// An irrecoverable SI_KERNEL SIGSEGV has occurred.
// It's likely caused by dereferencing an address larger than TASK_SIZE.
return false;
}
// 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
if (os::Posix::handle_stack_overflow(thread, addr, pc, uc, &stub)) {
return true; // continue
}
}
}
if ((sig == SIGSEGV) && VM_Version::is_cpuinfo_segv_addr(pc)) {
// Verify that OS save/restore AVX registers.
stub = VM_Version::cpuinfo_cont_addr();
}
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 /* && info->si_code == BUS_OBJERR */) {
// 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(pc);
CompiledMethod* nm = (cb != nullptr) ? cb->as_compiled_method_or_null() : nullptr;
bool is_unsafe_arraycopy = thread->doing_unsafe_access() && UnsafeCopyMemory::contains_pc(pc);
if ((nm != nullptr && nm->has_unsafe_access()) || is_unsafe_arraycopy) {
address next_pc = Assembler::locate_next_instruction(pc);
if (is_unsafe_arraycopy) {
next_pc = UnsafeCopyMemory::page_error_continue_pc(pc);
}
stub = SharedRuntime::handle_unsafe_access(thread, next_pc);
}
}
else
#ifdef AMD64
if (sig == SIGFPE &&
(info->si_code == FPE_INTDIV || info->si_code == FPE_FLTDIV)) {
stub =
SharedRuntime::
continuation_for_implicit_exception(thread,
pc,
SharedRuntime::
IMPLICIT_DIVIDE_BY_ZERO);
#else
if (sig == SIGFPE /* && info->si_code == FPE_INTDIV */) {
// HACK: si_code does not work on linux 2.2.12-20!!!
int op = pc[0];
if (op == 0xDB) {
// FIST
// TODO: The encoding of D2I in x86_32.ad can cause an exception
// prior to the fist instruction if there was an invalid operation
// pending. We want to dismiss that exception. From the win_32
// side it also seems that if it really was the fist causing
// the exception that we do the d2i by hand with different
// rounding. Seems kind of weird.
// NOTE: that we take the exception at the NEXT floating point instruction.
assert(pc[0] == 0xDB, "not a FIST opcode");
assert(pc[1] == 0x14, "not a FIST opcode");
assert(pc[2] == 0x24, "not a FIST opcode");
return true;
} else if (op == 0xF7) {
// IDIV
stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::IMPLICIT_DIVIDE_BY_ZERO);
} else {
// TODO: handle more cases if we are using other x86 instructions
// that can generate SIGFPE signal on linux.
tty->print_cr("unknown opcode 0x%X with SIGFPE.", op);
fatal("please update this code.");
}
#endif // AMD64
} else if (sig == SIGSEGV &&
MacroAssembler::uses_implicit_null_check(info->si_addr)) {
// Determination of interpreter/vtable stub/compiled code null exception
stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::IMPLICIT_NULL);
}
} else if ((thread->thread_state() == _thread_in_vm ||
thread->thread_state() == _thread_in_native) &&
(sig == SIGBUS && /* info->si_code == BUS_OBJERR && */
thread->doing_unsafe_access())) {
address next_pc = Assembler::locate_next_instruction(pc);
if (UnsafeCopyMemory::contains_pc(pc)) {
next_pc = UnsafeCopyMemory::page_error_continue_pc(pc);
}
stub = SharedRuntime::handle_unsafe_access(thread, next_pc);
}
// 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;
}
}
}
1条答案
按热度按时间wqsoz72f1#
将
kill -11
发送到java进程会引发NullPointerException吗?NullPointerException
是一个特定的异常,当应用程序试图使用具有null值的对象引用时会发生。然而,从JavaSE 17 /故障排除指南/处理信号和故障
Java HotSpot VM安装信号处理程序以实现各种功能并处理致命错误条件。
例如,在
java.lang.NullPointerException
很少被抛出的情况下,在避免显式空检查的优化中,SIGSEGV
信号被捕获和处理,而NullPointerException
被抛出。一般来说,有两类信号/陷阱发生:
SIGSEGV
,这导致执行一个存根,将线程带到安全点。SIGSEGV
。在这些情况下,信号是意外的,因此调用致命错误处理来创建错误日志并终止进程。这种方法允许JVM通过减少代码中显式空值检查的开销来优化性能,而是依赖于操作系统的内存保护机制来检测对空值引用的访问。当这种访问发生时,操作系统生成一个
SIGSEGV
信号,JVM然后将其解释为试图解引用空值指针,导致抛出NullPointerException
。但是,需要注意的是,这是JVM的一种内部机制,与外部生成的
SIGSEGV
信号不同,例如使用kill
命令发送的信号。外部SIGSEGV
信号通常用于指示严重错误,包括无效的内存访问,并且更有可能导致JVM崩溃或核心转储,而不是NullPointerException
。字符串
JVM是否总是能够检测外部
SIGSEGV
是否是外部SIGSEGV
,或者当外部SIGSEGV
在特定时间发生时(即当预期潜在的空访问时),是否可能将其混淆为空访问?同样,它不应该这样做,但这是JVM行为的一个特定于实现的方面。
这意味着在实践中发生这种混淆的可能性可能会因JVM版本、正在执行的特定代码以及信号发出时JVM的状态而异。
例如,参见“How does the JVM know when to throw a NullPointerException“
JVM可以使用虚拟内存硬件来实现空值检查,JVM将其虚拟地址空间中的页面零Map到不可读+不可写的页面。
由于null被表示为零,当Java代码试图解引用null时,这将试图访问一个不可寻址的页面,并将导致OS向JVM发送“segfault”信号。
JVM的segfault信号处理程序可以捕获它,找出代码在哪里执行,并在适当线程的堆栈上创建和抛出NPE。
在这种情况下,应该很容易区分来自代码执行中的捕获信号和来自OS的接收信号。
别名:Can a
SIGSEGV
in Java not crash the JVM?在某些情况下,JVM的
SIGSEGV
信号处理程序可能会将SIGSEGV
事件转换为Java异常。只有在JVM硬崩溃无法发生的情况下,才会发生这种情况;例如,当事件发生时,触发
SIGSEGV
的线程正在本机库中执行代码。例如:
HotSpot JVM故意在启动时生成SIGSEGV来检查某些CPU功能。没有开关可以关闭它。我建议完全跳过
gdb
中的SIGSEGV
,因为JVM在许多情况下会将其用于自己的目的。如果
SIGSEGV
在外部触发时堆栈恰好位于访问地址处,该怎么办?hotspot在JDK-8255711中的信号处理方面进行了重大重构,导致了commit dd8e4ff。
当前代码为
os_linux_x86.cpp#PosixSignals::pd_hotspot_signal_handler
型
JVM使用各种检查来确定
SIGSEGV
信号的上下文。然而,我没有看到一种直接的机制来区分外部发送的SIGSEGV
和内部由于空引用访问而生成的SIGSEGV
。信号处理程序检查执行上下文,包括程序计数器和堆栈,以推断
SIGSEGV
的原因。在空引用的情况下,它会查找暗示空指针异常的特定模式。但是如果外部SIGSEGV
恰好与JVM的执行状态类似于空指针访问的情况相一致,区分两者可能是一个挑战。然而,由于时间上所需的精确度,这种情况相对不太可能发生。