应优先分析 hs_err_pid.log 中的 problematic frame,它精准定位崩溃的 native 模块、函数及偏移;再结合 java/native frames 边界、dmesg 日志、/proc/pid/maps 和 addr2line/ghidra 等工具,区分 jvm bug、jni 错误、内存非法访问或内核级干扰。

看 hs_err_pid.log 里的 Problematic frame
OpenJDK 崩溃时生成的 hs_err_pid*.log 是第一手证据。重点不是堆栈顶部的 Java 方法,而是 Problematic frame 这一行——它明确指出崩溃发生在哪个 native 模块、哪个函数、偏移多少字节。
常见格式:C [libjvm.so+0x1a2b3c] jni_GetObjectClass+0x48 或 J 1234 com.example.NativeUtil.crash()V(J 表示 JIT 编译后的方法,需结合 Java frames 段交叉验证)。
- 如果 frame 显示
libjvm.so内部函数(如Unsafe_GetInt、os::os_exception_wrapper),大概率是 JVM 自身 bug 或 GC/JIT 线程异常,需核对 JDK 版本已知 issue - 如果 frame 指向第三方
.so(如libnet.so、libnio.so、或你自己的libxxx.so),问题就在该模块,立刻查其 JNI 调用逻辑 - 若 frame 是
libc.so或libpthread.so中的memcpy、free、pthread_mutex_lock,说明上层已传入非法地址或破坏了内存结构,要回溯调用链中谁构造/释放了该指针
确认崩溃是否由 JNI 引起:检查 Java/Native Stack 与 ExceptionCheck
在 hs_err_pid*.log 的 Java frames 和 Native frames 交界处,看是否有明显的 JNI 边界跳转。再结合崩溃上下文判断:
- 崩溃前一帧是
JNI_CreateJavaVM、CallStaticVoidMethod、GetObjectField等 JNI 函数 → 高概率是 JNI 层未检查返回值或误用局部/全局引用 - 崩溃堆栈末端停在
libjvm.so内部但 Java 层无任何异常日志 → 很可能是 Java 抛出异常后 C 代码没调用env->ExceptionCheck()就继续执行,后续访问了已被 GC 回收的对象字段 - 崩溃地址为
0x0或极小值(如0x0000000000000008)→ 典型空指针解引用,重点查GetObjectClass、GetMethodID返回是否为nullptr
用 dmesg 查内核级干扰:物理页回收或 OOM Killer 干预
不是所有 SIGSEGV 都来自代码缺陷。当 hs_err_pid*.log 中 Error Signal 是 SIGSEGV,但 Problematic frame 地址落在合法堆内存范围内(比如 0x7f8c3b6e1c04 在 mmap 区域内),且崩溃无规律、多线程复现率高,就要怀疑系统级干扰:
- 运行
dmesg -T | grep -i "killed process\|out of memory\|pagecache",确认是否触发 OOM Killer 或 page cache 回收 - 查
/proc/meminfo中MemFree、SwapCached、Active(file)是否长期低于阈值 - 如果服务使用大块堆外内存(如 Netty DirectBuffer、自定义内存池),且未调用
madvise(MADV_DONTFORK)或MADV_WILLNEED,内核可能在压力下回收其物理页,导致后续访问触发 SIGSEGV
用 addr2line 或 Ghidra 定位 so 文件中的具体代码行
当 Problematic frame 指向你的 .so(如 libdemo.so+0x1234),需要把偏移换算成源码行:
- 先用
readelf -S libdemo.so | grep "\.text"找到 .text 段 VMA(虚拟内存地址),再用addr2line -e libdemo.so -f -C 0x1234(注意:若 so 无调试符号,结果为空;需编译时加-g) - 若无符号表,用 Ghidra 导入
.so,在 Symbol Tree 中定位entry或按基址 + 偏移跳转,结合反编译窗口看控制流和内存操作 - 特别注意 ARM64 下 tombstone 中的 offset 是相对于 so 加载基址的,而
readelf -l输出的LOAD段 p_vaddr 是文件视角地址,二者不等价;应以cat /proc/<pid>/maps | grep libdemo.so</pid>获取运行时真实基址
真正难的不是找到崩溃点,而是确认那个地址为什么“合法却不可访问”——它可能刚被 free,可能被 mmap 保护为不可读,也可能物理页已被内核回收。别急着改业务逻辑,先让 dmesg 和 /proc/pid/maps 说话。











