本地方法栈溢出表现为 java.lang.OutOfMemoryError: unable to create new native thread,根源是操作系统线程资源耗尽而非 JVM 堆内存不足;排查重点为线程数爆炸或单线程本地栈过大,需检查 ulimit、/proc/sys/kernel/threads-max、jstack 线程数及线程池配置。

本地方法栈溢出在 Java 中通常表现为 java.lang.OutOfMemoryError: unable to create new native thread,而不是 StackOverflowError(后者属于 Java 虚拟机栈)。这是因为本地方法栈由操作系统管理,其内存来自系统原生内存,当线程创建失败、无法分配足够本地栈空间时,JVM 就会抛出该错误。排查重点不在“栈帧过深”,而在“线程数量爆炸”或“单个线程本地栈过大”。
看异常日志,确认是否为本地线程相关溢出
遇到 OOM 时,第一件事是检查完整错误信息:
- 出现 unable to create new native thread → 明确指向本地方法栈/线程资源耗尽
- 出现 Java heap space 或 Metaspace → 与本地栈无关,可排除
- 出现 Direct buffer memory → 属于直接内存,也不属于本地方法栈
注意:该异常本质是操作系统拒绝为新线程分配资源(如虚拟内存、句柄、栈空间),不是 JVM 堆内问题,所以堆转储(heap dump)对它帮助有限。
查系统级资源限制和线程数上限
Linux/macOS 下,线程本质是轻量级进程(LWP),受以下几类限制影响:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
-
用户级最大进程/线程数:用
ulimit -u查看;超限后所有pthread_create失败 -
虚拟内存总量:每个线程默认分配约 1MB(或由
-Xss影响 Java 栈,但本地栈另计)的栈空间;若设-Xss2m且创建 2000 个线程,仅栈就占 4GB 虚拟内存 -
文件描述符限制:
ulimit -n,某些框架(如 Netty)每连接可能开多个线程+FD,容易联动触顶 -
系统全局线程数:
cat /proc/sys/kernel/threads-max,部分云环境(如容器)默认值极低(如 512)
分析 Java 进程的线程实际使用情况
用工具确认是不是真有过多线程在运行:
-
jstack <pid>:输出所有线程栈,统计行数(
jstack 12345 | grep 'java.lang.Thread' | wc -l),观察是否远超预期(如 >500) - top -H -p <pid> 或 htop:查看 LWP 数量(即线程数),对比系统限制
- jstat -gc <pid> 配合 jstat -compiler <pid>:若线程暴增伴随大量 JIT 编译或 GC 次数异常,可能是动态代理、反射等触发类加载+线程创建
- 代码中检查:ExecutorService 是否未 shutdown、是否无限 submit;Timer/TimerTask 是否泄漏;第三方 SDK(如 OkHttp、Netty、Druid)是否配置了过大的线程池且未复用
验证并调整 JVM 与系统参数
不是所有场景都适合加内存,关键是匹配实际负载:
- 减小单线程栈大小:用
-Xss256k(而非默认 1M)可让同样内存支持更多线程(但需确保无深层递归) - 限制线程创建源头:如将
Executors.newCachedThreadPool()改为带界线程池(new ThreadPoolExecutor(2, 50, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(100))) - 容器环境特别注意:Docker/K8s 中需显式设置
--ulimit nofile=65536:65536 --ulimit nproc=4096:4096,否则默认可能只有几百 - 避免隐式线程生成:禁用
java.util.Timer,改用ScheduledThreadPoolExecutor;关闭 Log4j2 的异步日志后台线程(log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector会启多线程)
不复杂但容易忽略

















