本地方法栈溢出本质是操作系统线程栈被native层调用耗尽,非JVM栈问题,不响应-Xss;需通过极浅堆栈含native方法、hs_err日志SIGSEGV、jstack显示_thread_in_native等特征确认,并从JNI引用管理、系统栈限制及第三方库行为协同排查。

本地方法栈溢出不是 Java 虚拟机栈问题,而是操作系统线程栈被 native 层调用耗尽所致。它不响应 -Xss 参数,也不能靠增加 Java 栈大小解决。关键在于识别 native 层的失控行为,并从 JNI 引用管理、系统栈限制和第三方库行为三方面协同处理。
看堆栈特征确认是不是本地方法栈溢出
不能只看是否抛 StackOverflowError,要结合上下文判断:
- 异常堆栈深度极浅(通常 ≤ 5 层),顶层方法带
native标识,比如Java_java_nio_Bits_copyToArray或自定义 JNI 函数名; - JVM 崩溃生成
hs_err_pid*.log,其中siginfo: si_signo = 11 (SIGSEGV),且Stack:区域出现大量重复的.so调用帧; -
jstack输出中对应线程状态为_thread_in_native,堆栈为空或仅显示线程基本信息,无任何 Java 方法帧。
查 JNI 局部引用是否堆积
局部引用未及时释放是常见诱因,尤其在循环或嵌套 native 调用中:
- 每次调用
env->GetObjectClass()、env->GetMethodID()等都会创建局部引用,必须配对使用env->DeleteLocalRef(); - 避免在
PushLocalFrame()/PopLocalFrame()外大量创建引用; - Android 上局部引用表默认上限为 512,超限会直接报
local reference table overflow,需主动清理或改用全局引用(但要注意内存泄漏风险)。
验操作系统线程栈与 native 内存使用
本地方法栈依赖 OS 线程栈,资源限制直接影响稳定性:
- Linux 下执行
ulimit -s查当前线程栈大小(常见为 8192 KB),可临时增大验证:ulimit -s 16384后复现问题; - JVM 启动加
-XX:NativeMemoryTracking=detail,再用jcmd <pid> VM.native_memory summary观察Internal和Thread类别内存是否持续增长; - 用
perf record -e syscalls:sys_enter_mmap,syscalls:sys_enter_munmap -p <pid>捕获 mmap/munmap 频次,高频调用可能反映 DirectByteBuffer 或 native 库在频繁申请释放,间接加剧栈管理压力。
审第三方 native 组件行为
很多溢出实际来自封装好的 native 库,而非自写 JNI:
- Netty 的 epoll/kqueue transport、SQLite JDBC native driver、OpenCV Java binding、FFmpeg 封装库等,都可能因版本缺陷或配置不当引发本地栈耗尽;
- 查阅对应库的已知 issue,例如 Netty 4.1.100+ 修复过 epoll wait 导致的本地栈异常增长;
- 若使用 DirectByteBuffer,注意频繁
allocate()/free()可能触发底层 mmap/munmap,在某些内核版本下导致栈帧异常累积。

















