ulimit -u 限制用户级进程与线程总数,是导致 outofmemoryerror: unable to create new native thread 的最常见原因;需协同调整 limitnproc、threads-max 和 pid_max 四层限制,并注意 systemd 服务和容器环境的特殊配置。

ulimit -u 是最常被误读也最该优先调的“线程数限制”,它不是纯线程限制,而是用户级进程 + 线程总数上限。Linux 内核把线程当作轻量进程(LWP)处理,ulimit -u 一并约束两者。你看到 java.lang.OutOfMemoryError: unable to create new native thread,90% 情况下卡在这里,而不是堆内存或 GC 问题。
查清到底是哪一层在拦你
别一上来就改配置,先定位瓶颈点:
-
ps -eLf | wc -l—— 查当前系统总线程数 -
ulimit -u—— 查当前 shell 的用户级上限(普通用户常为 1024) -
prlimit -n $PID—— 查具体 Java/Python 进程实际继承的nproc值(注意:systemd 服务默认不读/etc/security/limits.conf) -
cat /proc/sys/kernel/threads-max—— 看内核级总线程上限,若ps -eLf | wc -l接近它,就是它卡死了 -
cat /proc/sys/kernel/pid_max—— 若ps -eLf | wc -l接近此值,且报fork: Cannot allocate memory,说明 PID 池耗尽
优先调 ulimit -u(最常见生效点)
90% 的 Java、Python 多线程服务卡在这层。临时生效只对当前终端及子进程有效:
ulimit -u 65535
永久生效需满足三个条件:
- 写入
/etc/security/limits.d/90-nproc.conf(CentOS 7+ 推荐路径,不是limits.conf),内容为:* soft nproc 65535* hard nproc 65535root soft nproc 65535root hard nproc 65535 - 确认
/etc/pam.d/common-session含session required pam_limits.so - 用户需重新登录(SSH 或 console),不是
source或重启终端
systemd 服务必须显式配 LimitNPROC
用 systemctl start myapp.service 启的服务完全无视 limits.conf。它走 systemd 资源控制路径,ulimit -u 对它无效。
- 编辑
/etc/systemd/system/myapp.service,在[Service]段加:LimitNPROC=65535 - 或全局生效:在
/etc/systemd/system.conf中设:DefaultLimitNPROC=65535,再运行systemctl daemon-reload - 验证是否生效:
systemctl show myapp.service | grep NPROC,或prlimit -n $(pgrep -f myapp)
别漏掉 pid_max 和 threads-max
当 ulimit -u 和 LimitNPROC 都调高了,ps -eLf | wc -l 却仍卡在几万,大概率是这两项没跟上:
-
pid_max是所有进程/线程分配 PID 的池子,必须 ≥ 目标threads-max,否则写入会静默失败或报Invalid argument。
临时调高:sudo sysctl -w kernel.pid_max=4194304
永久:在/etc/sysctl.conf加kernel.pid_max = 4194304,再执行sysctl -p -
threads-max是内核级硬闸,真正卡死新建线程的开关。
临时改:echo 2097152 | sudo tee /proc/sys/kernel/threads-max
永久:写入/etc/sysctl.d/99-thread-limit.conf,内容为kernel.threads-max = 2097152,再执行sudo sysctl --system
复杂点在于四层限制(ulimit -u、LimitNPROC、threads-max、pid_max)要协同生效,且容器/K8s 环境还需额外传参或配 securityContext.limits.nproc——漏掉任何一层,都可能在高并发时悄无声息地 fallback 到默认值。











