该错误非堆内存不足,而是操作系统无法为JVM分配新本地线程,主因是线程栈、内核描述符等资源达OS或JVM限制;需调小-Xss、禁用newCachedThreadPool、改用有界线程池、检查第三方库线程配置、确认ulimit -u和/proc/sys/kernel/threads-max,并排查线程泄漏。

这个错误不是堆内存不够,而是操作系统无法给 JVM 分配新的本地线程。核心矛盾在于:线程创建要占系统资源(栈空间、内核线程描述符等),一旦触达 OS 或 JVM 的限制,就会直接失败。
调小单个线程的栈大小(-Xss)
64 位 Linux 上 JVM 默认栈大小常为 1MB,1000 个线程就吃掉约 1GB 虚拟地址空间——在容器或 32 位环境里极易爆掉。
- 查当前值:
java -XX:+PrintFlagsFinal -version | grep ThreadStackSize - 合理下调,比如
-Xss256k或-Xss512k;过小可能引发StackOverflowError,尤其用到深度递归或 Spring WebFlux/Netty 等框架时需实测 - 对定时任务、WebSocket、异步回调等高频线程场景,可单独配置其线程池的
ThreadFactory并设小栈
严格控制线程总数,禁用无界创建
业务代码里随手 new Thread()、用 Executors.newCachedThreadPool()、没设上限的 HTTP 客户端连接池,都是“线程喷子”。
- 彻底停用
newCachedThreadPool,改用有界线程池:new ThreadPoolExecutor(core, max, keepAlive, unit, new LinkedBlockingQueue(queueSize)) - 检查所有第三方库:OkHttp 的
ConnectionPool、HikariCP 的maximumPoolSize、RabbitMQ 的 consumer 数、Netty 的EventLoopGroup线程数,全部显式设上限 - 避免每次请求都 new 客户端(如 new OkHttpClient()),应复用静态实例
确认并放宽系统级线程限制
Linux 中真正卡脖子的是两个值:ulimit -u(用户级进程/线程总数)和 /proc/sys/kernel/threads-max(全系统线程上限)。
立即学习“Java免费学习笔记(深入)”;
- 查当前限制:
ulimit -u和cat /proc/sys/kernel/threads-max - 临时提升(需 root):
ulimit -u 65535,echo 131072 > /proc/sys/kernel/threads-max - Docker 启动时加参数:
--ulimit nproc=65535:65535,否则默认继承宿主机且可能被静默截断
排查隐式线程泄漏
线程没关干净,数量会缓慢上涨,日志里看不出明显暴增,但几小时后突然崩掉——这种最难定位。
- 未
shutdown()的ScheduledExecutorService - 未
close()的ForkJoinPool或未释放的ThreadLocal持有对象 - 用
jstack <pid>抓堆栈,统计java.lang.Thread.State非 RUNNABLE 的线程数,持续增长就是泄漏信号


















