unable to create new native thread 是操作系统拒绝分配线程,需先查进程线程数、ulimit -u、threads-max 和 threadstacksize,再调小 -xss、禁用 newcachedthreadpool、检查第三方库线程配置,并排查线程泄漏。

这不是堆内存不够,是操作系统拒绝给 JVM 分配新线程。OpenJDK 和系统内核之间是 1:1 线程映射,unable to create new native thread 意味着要么系统资源(栈空间、线程描述符)耗尽,要么应用自己把线程数撑爆了。
查当前进程线程数和系统限制
先确认是不是真到了上限,别一上来就改参数:
- 看本进程开了多少线程:
ls /proc/<em>PID</em>/task | wc -l(PID 替换为实际值) - 看用户级线程上限:
ulimit -u—— 常见默认是 1024 或 4096,远低于业务需要 - 看系统全局线程上限:
cat /proc/sys/kernel/threads-max - 查 JVM 实际用了多大栈:
java -XX:+PrintFlagsFinal -version | grep ThreadStackSize,OpenJDK 64 位默认通常是1024(单位 KB)
调小 -Xss 是最直接有效的缓解手段
每个线程默认占 1MB 栈空间,1000 个线程就是 1GB 虚拟地址空间 —— 尤其在容器或 32 位环境里极易崩。不是所有线程都需要这么大:
- 普通业务线程(HTTP handler、定时任务)用
-Xss256k完全够用 - 递归深或用了大量本地变量的线程,可设为
-Xss512k,但避免盲目设-Xss128k,否则容易触发StackOverflowError - Spring Boot 中 WebSocket、异步日志等组件会创建额外线程,建议统一加
-Xss,别只调主线程池
禁用 Executors.newCachedThreadPool(),改用有界线程池
这个工厂方法是“线程泄漏加速器”:无界队列 + 60 秒空闲自动销毁 + 无限新建线程。一旦请求堆积或响应变慢,线程数会指数级上涨:
- 替换成显式构造的
ThreadPoolExecutor,例如:new ThreadPoolExecutor(4, 32, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(100)) - 所有第三方库必须检查:OkHttp 的
ConnectionPool、HikariCP 的maximumPoolSize、Netty 的EventLoopGroup线程数、ForkJoinPool 并行流默认并行度(ForkJoinPool.getCommonPoolParallelism()) - 特别注意
CompletableFuture.supplyAsync()—— 默认走 common pool,高并发下极易打满
排查线程泄漏比调参更重要
线程没 shutdown、ThreadLocal 持有对象不清理、未 close 的 ForkJoinPool 或 ScheduledExecutorService,都会让线程长期存活:
- 用
jstack <em>PID</em>抓快照,搜java.lang.Thread.State: TIMED_WAITING或RUNNABLE但明显不该长期存在的线程名(比如async-task-*、apollo-refresh-*) - 对比多个时间点的线程数增长趋势,确认是否持续上升
- 检查
ThreadFactory实现 —— 是否漏传setDaemon(true),导致 JVM 无法退出时线程还卡着
真正难定位的是那些“半隐性泄漏”:比如某个 SDK 内部缓存了 ThreadLocal 并复用线程,但文档没写清楚;或者容器里 --ulimit nproc 覆盖了宿主机设置却没被监控覆盖到。这类问题必须结合 jstack + ulimit -u + 启动参数三者交叉验证,单看日志容易误判。











