一句话先说结论:这个报错和 Java 堆没关系。它说的是 JVM 找操作系统申请原生线程失败,原因要么是线程数顶到 OS 上限,要么是线程栈把 native 内存吃空了。先查
ulimit -u和线程数,再决定调大限制还是减线程。
背景
Java 的每个线程,背后都对应一个操作系统原生线程。原生线程的开销不进 Java 堆,走的是 native 内存。
线程栈是 native 内存的大头。64 位 JVM 默认一个线程栈约 1 MB。堆可能还有一半空着,线程数一多,native 内存先扛不住。
所以这个报错经常出现在监控显示「内存还很充足」的时候,容易让人一头雾水。
现象
线程池或压测脚本不停开线程,跑到某一刻报错:
java.lang.OutOfMemoryError: unable to create new native thread
at java.base/java.lang.Thread.start0(Native Method)
at java.base/java.lang.Thread.start(Thread.java:809)
堆栈落在 Thread.start0,这是开原生线程的入口。注意它和 OutOfMemoryError: Java heap space 是两码事,后者才是堆满。
根因分析
两类原因,按频率排。
1. 线程数撞上操作系统的上限
Linux 把进程和线程当同一种东西管。下面的限制直接影响能开多少线程:
ulimit -u:每个用户能开的进程/线程数,常见默认是 1024。kernel.threads-max:系统级线程总数上限。kernel.pid_max:最大 PID,也间接卡住线程数。- 容器里的 cgroup
pids限制。 - systemd 管理的服务,另有一个
UserTasksMax。
2. native 内存被线程栈耗尽
每个线程栈默认约 1 MB。开 1 万个线程,光栈就要约 10 GB native 内存。线程多,或者 -Xss 设太大,都会把 native 内存耗光。
排查步骤
先确认是哪种原因。
看用户线程上限:
ulimit -u
看系统级参数:
cat /proc/sys/kernel/threads-max
cat /proc/sys/kernel/pid_max
cat /proc/sys/vm/max_map_count
数一下当前进程开了多少线程:
jstack <pid> | grep "java.lang.Thread.State" | wc -l
线程数贴近限制,基本就定位了。
解决方案
按根因对症下药。
调高用户线程上限
临时放开,让这个 shell 能开更多线程:
ulimit -u 8192
永久改,写 /etc/security/limits.conf:
foo hard nproc 50000
foo soft nproc 50000
foo hard nofile 60000
foo soft nofile 60000
调内核参数
系统级上限不够时:
sysctl -w kernel.threads-max=100000
sysctl -w kernel.pid_max=400000
sysctl -w vm.max_map_count=500000
写进 /etc/sysctl.conf 才能重启后生效。
systemd 服务改 UserTasksMax
用 systemd 拉起的服务,检查 /etc/systemd/system.conf:
UserTasksMax=60000
改完执行 systemctl daemon-reload。
从根上减线程
调限制是治标,治本是少开线程。
- 把无界的
newCachedThreadPool换成有界线程池。 - 控制压测和并发度。
- 真有大量并发,Java 21 的虚拟线程更合适,它不占原生线程。
减小线程栈
确需大量原生线程时,可以压缩单个栈:
java -Xss512k ...
默认 1 MB,降到 512 KB 能让同等 native 内存容纳更多线程。但栈太小有 StackOverflowError 风险,得实测。
思考过程
排查时最容易犯的错,是看到 OutOfMemoryError 就调大 -Xmx。这个错误不归 -Xmx 管,调了没用。
先分清「堆满」和「线程满」是两类问题。堆满看 Java heap space,线程满是 unable to create new native thread。前者加堆,后者查 OS 限制和线程数。
容器环境还多一层:宿主机 ulimit -u 放开了,但容器 cgroup 的 pids 限制没调,照样开不了线程。
小结
- 这个报错是 OS 拒绝创建新原生线程,和 Java 堆无关。
- 根因:线程数顶到 ulimit/内核/容器限制,或线程栈耗光 native 内存。
- 先
ulimit -u加数线程定位,再决定调限制还是减线程。 - 治本是少开线程,或上虚拟线程。
- 别拿
-Xmx去修,方向错了。