1. 问题处理记录
1.1 问题描述
-
公司使用Presto作为OLAP查询引擎,Presto的coordinator节点在运行过程中报错
java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reachedat java.base/java.lang.Thread.start0(Native Method)at java.base/java.lang.Thread.start(Thread.java:802)
-
当时,第一反应:有了异常堆栈,大概知道是哪里发生了线程滥用,这是测试环境,先立马重启服务后观察下线程增长情况
-
重启服务后,不停执行如下命令,发现对应Java进程的线程使用量确实再不断增加
cat /proc/${pid}/status | grep "Threads"
1.2 问题修复与观察
-
问题原因: 通过异常堆栈,发现是某个方法不停新建HttpClient、却又没有进行close导致
-
问题修复后,通过如下shell脚本观察线程使用情况:每隔10min打印一次线程数
#!/bin/bashwhile true doecho -ne $(date +%Y-%m-%dT%H:%M:%S)" "cat /proc/${pid}/status | grep "Threads" # 使用时,${pid}替换成对应的Java服务进程号sleep 10m done
-
定时执行shell脚本,发现线程数得到了控制,观察4小时,最大线程数699 —— 问题成功修复
nohup ./watch_threads.sh > threads_stat.txt 2>&1 &
2. 疑问:一个Java进程究竟能创建多少线程?
-
后来,使用
ulimit -a
查看系统资源配置时,发现系统允许root用户可以创建的最大进程或线程数为unlimited
-
因此,笔者有了疑惑:
- 既然是unlimited,为何会出现
unable to create native thread
的情况? - 还有,从系统资源有限的角度来说,这里的unlimited不应该是无上限,而是相对具体的、类似
65636
这样的固定值来说,只要系统资源未耗尽就可以创建线程
- 既然是unlimited,为何会出现
-
可惜的是,笔者当时并未查看报错时的线程数,无法判断这个
unlimited
的上限究竟是多少
2.1 优秀文章的启发:How Many Threads Can a Java VM Support?
- Baeldung的一篇文章:How Many Threads Can a Java VM Support?,让笔者大概有了一个认识
- JVM,即一个Java进程,能创建的最大线程数,至少有两个方面的决定因素:
- JVM本身 + 系统内存
- 操作系统限制
2.2 决定因素1:JVM本身 + 系统内存
- JVM中,堆栈的max size由
-Xss
或-XX:ThreadStackSize
进行设置-Xss1024k # 如果省略unit,默认unit:Byte -XX:ThreadStackSize=1024 # unit:KB
- JVM会为每个线程创建自己的堆栈,创建堆栈使用的内存为系统内存,并非JVM Heap memory
- 因此,有如下计算公式
MaxProcessMemory
:是指进程的最大寻址空间ReservedOsMemory
:保留的操作系统内存,如Native Heap、JNI之类,一般100MB
Number of threads = (MaxProcessMemory - JVMHeapMemory - ReservedOsMemory) / (ThreadStackSize)
2.3 决定因素2:操作系统限制(以Linux系统为例)
-
首先,Linux系统级对max thread的限制,threads-max
cat /proc/sys/kernel/threads-max # 服务器上的threads-max = 6179160
-
其次, Linux系统在内核级别将线程视为进程,因此限制最大进程数的pid_max也会影响JVM可以创建的线程数量
-
这也是为什么
ulimit -a
展示的max user processes也被视为线程数限制的原因cat /proc/sys/kernel/pid_max # 相对threads-max,值更小,4194303
-
还有,vm.max_map_count 参数,它指定进程可以拥有的虚拟内存区域 (VMAs,Virtual Memory Areas) 的最大数量
cat /proc/sys/vm/max_map_count # 三者中,max_map_count的值最小,2097152
-
最后,Linux支持针用户级别的资源限制,可以通过
ulimit -a
查看当前用户的资源限制。其中,max user processes的值也限制了JVM能创建的最大线程数
2.4 总结
- JVM能创建的最大线程数是由各种因素综合决定的,且一定是这些因素中的min value决定
- 决定因素1触发
unable to create native thread
,其根因是内存不足 - 决定因素2中触发
unable to create native thread
,其本质是系统资源限制
3. 后记
- 后续,如果有机会可以学习:
- 如何触发
unable to create native thread
? - 如何调整JVM参数、Linux系统的线程limit,从而验证这些影响因素
- 如何触发
- 一些优秀参考链接:
- 启蒙文章:How Many Threads Can a Java VM Support?
- unable to create new native thread 问题处理,指出了该出现错误的两种原因:内存不足或达到线程数限制
- Maximum Number of Threads per Process in Linux
- How to Get the Number of Threads in a Java Process,可以学习如何通过
ps
命令查看线程数 - 非常简单的触发
unable to create native thread
错误的demo:java jvm 最大线程数设置 - 对决定因素1中计算公式的介绍:JVM最多能创建多少个线程: unable to create new native thread