背景
java.lang.OutOfMemoryError 共有 8 种类型,其中 java.lang.OutOfMemoryError: unable to create new native thread 是很常见的一种,这类错误通常发生在应用试图创建新线程时。最近测试环境经常出现错误如下:
Caused by: java.lang.OutOfMemoryError: unable to create new native thread at java.lang.Thread.start0(Native Method) at java.lang.Thread.start(Thread.java:714) at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:950) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1368) at com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler.caught(AllChannelHandler.java:65)
可能原因
- 系统内存耗尽,无法为新线程分配内存
- 创建线程数超过了操作系统的限制
解决方案
-
排查应用是否创建了过多的线程
通过 jstack 确定应用创建了多少线程?超量创建的线程的堆栈信息是怎样的?谁创建了这些线程?一旦明确了这些问题,便很容易解决。步骤如下:
该进程内最耗费 CPU 的线程 pid top -Hp pid
将 pid 装换成十六进制 printf “%x\n” 21742
最后用 jstack 查找线程堆栈信息 jstack 21711 | grep 54ee
-
调整操作系统线程数阈值
操作系统会限制进程允许创建的线程数,使用 ulimit -u 命令查看限制。某些服务器上此阈值设置的过小,比如 1024。一旦应用创建超过 1024 个线程,就会遇到 java.lang.OutOfMemoryError: unable to create new native thread 问题。如果是这种情况,可以调大操作系统线程数阈值。
用当前用户登录,然后用 ulimit -a 查看配置项,将 max user processes 项调整大一点,可以参考 top -H 信息中的 Threads: 853 total 线程数
-
增加机器内存
如果上述两项未能排除问题,可能是正常增长的业务确实需要更多内存来创建更多线程。如果是这种情况,增加机器内存。
-
减小堆内存
一个老司机也经常忽略的非常重要的知识点:线程不在堆内存上创建,线程在堆内存之外的内存上创建。所以如果分配了堆内存之后只剩下很少的可用内存,依然可能遇到 java.lang.OutOfMemoryError: unable to create new native thread。考虑如下场景:系统总内存 6G,堆内存分配了 5G,永久代 512M。在这种情况下,JVM 占用了 5.5G 内存,系统进程、其他用户进程和线程将共用剩下的 0.5G 内存,很有可能没有足够的可用内存创建新的线程。如果是这种情况,考虑减小堆内存。
-
减少进程数
这和减小堆内存原理相似。考虑如下场景:系统总内存 32G,java 进程数 5 个,每个进程的堆内存 6G。在这种情况下,java 进程总共占用 30G 内存,仅剩下 2G 内存用于系统进程、其他用户进程和线程,很有可能没有足够的可用内存创建新的线程。如果是这种情况,考虑减少每台机器上的进程数。
-
减小线程栈大小
线程会占用内存,如果每个线程都占用更多内存,整体上将消耗更多的内存。每个线程默认占用内存大小取决于 JVM 实现。可以利用-Xss 参数限制线程内存大小,降低总内存消耗。例如,JVM 默认每个线程占用 1M 内存,应用有 500 个线程,那么将消耗 500M 内存空间。如果实际上 256K 内存足够线程正常运行,配置-Xss256k,那么 500 个线程将只需要消耗 125M 内存。(注意,如果-Xss 设置的过低,将会产生 java.lang.StackOverflowError 错误)
我遇到的问题是 okhttp 发送异步请求,会使用线程池。