JNI引发的Native内存溢出本质是native heap持续占用未释放,耗尽系统内存,表现为OOM Killer杀进程、ANR或Direct buffer memory异常;需重点排查局部/全局/弱全局引用泄漏、字符串与数组API未配对释放、以及高风险架构设计。

JNI调用引发的Native内存溢出,本质是本地堆(native heap)被持续占用却未释放,最终耗尽系统可用内存。这类问题不会触发Java堆的OutOfMemoryError: Java heap space,而常表现为进程被系统OOM Killer强制终止、ANR、Crash(如SIGSEGV/SIGABRT)、或OutOfMemoryError: Direct buffer memory等间接信号——关键要意识到:JNI本身不分配Java堆内存,但会大量申请native memory(比如通过malloc、NewGlobalRef、GetStringUTFChars、NewDirectByteBuffer等),而这些不受JVM GC管理。
重点排查三类JNI引用泄漏
局部引用虽自动回收,但在循环或长生命周期native函数中未主动调用DeleteLocalRef,会导致JNI局部引用表填满(JVM报java.lang.OutOfMemoryError: JNI local reference table overflow);全局引用一旦创建就必须配对DeleteGlobalRef,漏掉一个就锁住对应Java对象及其所有可达对象,长期积累直接拖垮native heap;弱全局引用虽可被GC回收,但若未调用DeleteWeakGlobalRef,仍可能造成资源残留。
- 检查所有
NewGlobalRef调用点,确认每个都有且仅有一个对应的DeleteGlobalRef,尤其注意异常分支和提前return路径 - 在for循环内频繁调用
FindClass、NewObject、GetObjectField时,立即用DeleteLocalRef清理,不要依赖自动回收 - 使用
GetEnv获取JNIEnv前,先确认是否已附加线程(AttachCurrentThread),避免重复Attach导致隐式引用堆积
字符串与数组操作必须配对释放
JNI提供多组“获取-释放”成对API,它们从Java对象中提取原始数据并分配native memory,但JVM不会自动回收这些内存。例如:GetStringUTFChars返回char*指针,必须用ReleaseStringUTFChars归还;GetByteArrayElements返回jbyte*,需配ReleaseByteArrayElements;NewDirectByteBuffer创建的缓冲区,若底层内存由native malloc分配,必须自行free(),不能只靠ByteBuffer对象的finalize。
- 禁止在未调用Release系列函数的情况下让函数返回——哪怕只是日志打印后就return
- 优先使用
GetStringUTFRegion或GetStringRegion替代GetStringUTFChars,前者不分配新内存,更安全 - 处理大数组时,避免一次性
GetXXXArrayElements全部拷贝,改用GetXXXArrayRegion分段读取
监控native内存增长与线程行为
Linux下可通过/proc/[pid]/status中的VmRSS(实际物理内存)和VmSize(虚拟内存)观察整体增长趋势;用pstack [pid]或gdb -p [pid]查看native线程栈,确认是否存在阻塞在malloc、memcpy或JNI函数中的长时调用;Android平台可结合adb shell dumpsys meminfo [package]观察"Native Heap"字段变化,并开启adb shell setprop debug.jni.logging 1捕获JNI调用日志。
立即学习“Java免费学习笔记(深入)”;
- 在关键JNI入口/出口添加
mallinfo()或malloc_stats()快照(仅调试环境),对比调用前后allocated字节数 - 对高频调用的JNI方法做采样hook,统计
NewGlobalRef/DeleteGlobalRef调用次数是否平衡 - 检查是否误用
env->NewStringUTF传入非UTF-8字节流——可能触发内部多次realloc失败
规避设计层面的高风险模式
很多Native内存问题源于架构选择不当。例如:在Java层每秒创建数百个Bitmap,再通过JNI传给C++做图像处理,每次传递都生成全局引用+拷贝像素数据;或在C++侧用static map缓存jobject,却不做生命周期管理;又或者在子线程中反复AttachCurrentThread却忘记DetachCurrentThread,导致线程本地存储泄漏。
- Java对象尽量只传ID或序列化数据,而非jobject本身;必要持有时,用弱全局引用+回调通知机制替代强引用
- 将大块native内存分配封装为池化对象(如内存池、对象池),避免频繁malloc/free碎片化
- JNI方法尽量无状态、幂等,避免在native侧维护Java对象图谱——把复杂逻辑留在Java层,native只做原子计算


















