线程创建失败通常卡在 ulimit -u(用户级限制),因其同时约束进程与线程总数且最常见生效;其次需检查 /proc/sys/kernel/threads-max 和 /proc/sys/kernel/pid_max 是否匹配,三者任一触顶均导致 java.lang.outofmemoryerror: unable to create new native thread。

查清到底是哪一层在卡线程创建
Java 或 Go 应用报 java.lang.OutOfMemoryError: unable to create new native thread,但堆内存充足,别急着调 JVM 参数。Linux 线程数限制是三层叠加的:ulimit -u(用户级)、/proc/sys/kernel/threads-max(内核级)、/proc/sys/kernel/pid_max(PID 池上限)。任一层触顶都会失败。
快速定位方法:
-
ps -eLf | wc -l查当前总线程数 -
ulimit -u查当前 shell 的用户级上限(普通用户常为 1024) -
prlimit -n $PID查具体进程实际继承的nproc值(注意:systemd 服务默认不读limits.conf) -
cat /proc/sys/kernel/threads-max和cat /proc/sys/kernel/pid_max对比上面结果,看是否接近
优先调 ulimit -u(最常见生效点)
90% 的线程创建失败卡在这层——因为 ulimit -u 同时约束进程 + 线程总数(Linux 中线程即轻量进程),而 Java 进程启动时直接继承 shell 的该限制。
- 临时生效(当前终端及子进程):
ulimit -u 65535 - 永久生效(新登录会话):编辑
/etc/security/limits.d/90-nproc.conf,写入:* soft nproc 65535* hard nproc 65535
root 用户也要单独配:root soft nproc 65535和root hard nproc 65535 - systemd 服务不读
limits.conf,必须在 service 文件[Service]段加:LimitNPROC=65535,或全局设DefaultLimitNPROC=65535在/etc/systemd/system.conf - Docker 容器需显式传参:
docker run --ulimit nproc=65535:65535
改 /proc/sys/kernel/threads-max 要同步检查 pid_max
这个值是内核能支撑的全局线程总数,不是 per-user,也不是 per-cgroup。默认 ≈ mem_total_kb / (4 * page_size),物理内存越大初始值越高,但不是无限涨。
- 关键约束:
pid_max必须 ≥threads-max,否则写入会静默截断,报Invalid argument - 临时修改:
echo 2097152 > /proc/sys/kernel/threads-max(需 root 权限) - 永久配置:写入
/etc/sysctl.d/99-thread-limit.conf,内容为kernel.threads-max = 2097152,再执行sysctl --system - RHEL/CentOS 7+ 可能需额外
systemctl restart systemd-sysctl
别漏掉 pid_max(系统级 PID 池天花板)
它不是直接限制线程数,但它是所有进程/线程分配 PID 的池子。当 ulimit -u 和 threads-max 都调高了,却还是 fork 失败,大概率是它没跟上。
- 临时调高:
sysctl -w kernel.pid_max=4194304 - 永久配置:在
/etc/sysctl.d/99-pid-limit.conf中写入kernel.pid_max = 4194304,再执行sysctl --system - x86_64 下
pid_max上限约 4194304,threads-max不应显著超过它 - 容器环境里修改只对当前 namespace 生效,宿主机和其他容器不受影响
真正卡住线程创建的,往往不是最显眼的那个参数,而是某一层被忽略的依赖关系——比如改了 threads-max 却忘了同步拉高 pid_max,或者给普通用户配了 limits.conf 却没给 systemd 服务单独加 LimitNPROC。











