JNI内存泄漏与崩溃本质是Java GC机制与Native手动内存管理脱节所致:需双线排查——用hs_err_pid.log定位SIGSEGV等崩溃信号及Native栈帧,JFR监控全局引用创建/删除差值,NMT detail追踪malloc未free源头,并交叉验证Native Heap趋势。

JNI 调用引发的内存泄漏和崩溃,本质是 Java 与 Native 两层内存管理脱节所致:Java 层依赖 GC 自动回收,而 Native 层必须手动管理——稍有疏漏,轻则内存持续上涨,重则段错误崩溃。排查不能只盯 Java 堆,得双线并行、工具联动、现象反推。
看 hs_err_pid.log 定位崩溃类型与现场
JVM 崩溃必生成 hs_err_pid*.log,这是第一手证据:
- 开头查 siginfo 段:SIGSEGV(非法内存访问)、SIGABRT(断言失败)、SIGBUS(对齐/映射异常)直接指向 JNI 侧问题,比如空指针解引用、越界读写、未初始化的 JNIEnv*
- 翻到 Native frames 区域:若栈帧含
Java_*符号或你自己的libxxx.so函数名,基本确认崩溃发生在你的 native 方法中 - 注意 Current thread 是否为 AttachThread(非 Java 创建的线程):这类线程不自动 detach,JNIEnv* 无效、全局引用不自动清理,极易引发崩溃或泄漏
用 JFR 抓全局引用泄漏增长点
全局引用(GlobalRef)不释放,会阻止 Java 对象回收,导致 Full GC 频发、老年代堆积,但堆 dump 看不到“泄漏对象”——因为对象本身还在,只是被 Ref 卡住。
- 启动加参数:
-XX:+FlightRecorder -XX:StartFlightRecording=duration=120s,settings=profile -J-XX:+UnlockDiagnosticVMOptions - 分析 JFR 文件,重点筛选 JNIGlobalReferenceCreated 和 JNIGlobalReferenceDeleted 事件
- 对比差值:若某类(如
java.lang.Class或自定义业务类)创建远多于删除,且调用栈反复出现同一 native 方法,就是泄漏源 - 特别警惕在
AttachCurrentThread后创建、却未在DetachCurrentThread前释放的 GlobalRef
用 NMT detail 追 Native 内存 malloc 源头
Native 内存泄漏不会体现在堆里,top 看 RES 持续上涨、但 jmap -heap 正常,大概率是 malloc 未 free。
立即学习“Java免费学习笔记(深入)”;
- 启动必须带:
-XX:NativeMemoryTracking=detail -XX:+UnlockDiagnosticVMOptions - 运行中执行:
jcmd <pid> VM.native_memory detail | grep -A 3 -B 2 "libxxx\.so\|Java_com_.*"</pid> - 盯三处:
malloc=SIZE(单次大小)、#N(累计次数)、调用栈是否含你的 so 符号——若 #N 持续涨、无对应free=记录,即泄漏点 - 编译 so 时务必保留调试符号(
-g),否则调用栈只显示地址偏移,无法定位 C++ 函数
交叉验证缩小范围,避免误判
单靠一个工具容易走偏,需结合现象互证:
- 如果
VM.native_memory summary中 Internal 或 Other 区域持续上涨,而 Thread/Class/Code 稳定 → JNI malloc 泄漏优先级高于 GlobalRef - 如果 Full GC 频繁、但老年代使用率不高、新生代 GC 正常 → 全局引用阻塞回收,应主查 JFR,而非 NMT
- Android 平台可同步跑:
adb shell dumpsys meminfo <package></package>,观察 Native Heap 趋势,与 NMT 数据对照


















