高并发场景下经常会出现java.lang.OutOfMemoryError
。在所有的场景中java.lang.OutOfMemoryError: unable to create new native thread
是最常见的场景之一。当应用程序无法创建新线程时会生成这种类型。出现此错误,一般都是如下两个原因导致:
内存中没有空间容纳新线程。
线程数超过操作系统限制。
出现无法创建native thread场景复现
搜索下日志,会发现海量日志系统中存在此类异常。
java.lang.OutOfMemoryError: Unable to create new native thread .....
此异常并不会导致服务宕机,当次请求一定5xx。出现该问题一定会经过如下几个阶段:
运行在 JVM 中的应用程序收到一个新的 Java 请求创建线程;
JVM 系统会把创建新线程的请求转到操作系统;
操作系统尝试创建新线程,并为该线程分配内存;
如果已经超过操作系统的最大线程数限制,或者堆外内存不足,操作系统会拒绝创建线程,紧接着
java.lang.OutOfMemoryError: Unable to create new native thread error is thrown.
。
通过如下代码可以验证自身系统可以创建的最大线程数量:
public class TestThread extends Thread {
private static final AtomicInteger count = new AtomicInteger();
public static void main(String[] args) {
while (true)
(new TestThread()).start();
}
@Override
public void run() {
System.out.println(count.incrementAndGet());
while (true)
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
break;
}
}
}
执行如下命令,到达线程创建上限后,则会抛出异常。
javac TestThread.java
java TestThread
解决方法
该类问题很难杜绝,除非你在上线之前做好万全的准备,根据自身经验说说,如何才能在一定程度上避免该问题的出现。
修改操作系统线程限制。
操作系统可以创建的线程数存在限制。可以通过发出ulimit –u
命令找到限制。在某些服务器上,这个值设置较低,例如 1024。这意味着在这台机器上总共只能创建 1024 个线程。因此,如果您的应用程序正在创建超过 1024 个线程,它将遇到java.lang.OutOfMemoryError: unable to create new native thread.
在这种情况下,可以修改此限制。
如果使用了K8s pod的话,那么需要修改容器pids-limit限制,具体可以参考:https://cloud.tencent.com/developer/article/1428964
, 可以调大,要进行评估,建议不要无限大,因为该物理机不一定只运行一个Java进程。
为机器分配更多的内存。
线程不是在 JVM 堆中创建的。它们是在 JVM 堆之外创建的。所以如果 RAM 中剩余的空间较少,在 JVM 堆分配完成内存后,应用程序将遇到java.lang.OutOfMemoryError: unable to create new native thread.
可能性更大。http://javaeesupportpatterns.blogspot.com/2012/09/outofmemoryerror-unable-to-create-new.html
例如:
整体内存大小:6 GB
堆大小(即 –Xms 和 –Xmx):5 GB
Perm Gen 大小(即 -XX:MaxPermSize 和 -XX:MaxPermSize):512 MB
根据此配置,JVM 堆使用 5.5 GB(即 5 GB 堆 + 512 MB Perm Gen)并且只留下 0.5 GB(即 6 GB – 5.5 GB)空间。注意这 0.5 GB 空间 - 内核进程、其他用户进程和线程必须运行。一般情况下Java线程大小配置为1Mb.如果您的应用程序有 500 个线程,那么仅线程就将占用 500mb 的空间。为了缓解这个问题,您可以考虑将堆大小从 5GB 减少到 4GB(如果您的应用程序可以容纳它而不会遇到其他内存瓶颈);另外一种方式就是使用 java 系统属性 –Xss 来设置线程的内存大小。使用此属性,您可以减少内存大小。例如,如果您配置-Xss256k,您的线程将仅消耗 125mb 的空间。有人给出了一个根据堆外内存计算线程大小的公式:https://www.huaweicloud.com/articles/71aee8421026a5bda51ce56b5f12c27f.html
另外如果使用k8s进行部署,一般会在编排文件层面限制容器内存或CPU大小,所以尽量不要使用 xms,xmx 参数,而要使用JVM内存参数新增了MaxRAMPercentage、InitialRAMPercentage、MinRAMPercentage,较灵活设定JVM 大小。例如:如上POD为1G内存,通用的启动脚本中指定80%(-XX:MaxRAMPercentage=80.0 -XX:InitialRAMPercentage=80.0 -XX:MinRAMPercentage=80.0)
。那么服务就相当于设置了-Xmx819m -Xms819m
。
总结
上文主要介绍了一些配置技巧,当然我们还要进行上线前的压力测试,准确评估服务qps、资源占用、延迟,如果超过要进行限流或者扩容(性能不够,机器来凑),以及上线后的监控告警,通过这种方式可以从系统层面杜绝此类异常出现。
推荐
原创不易,随手关注或者”在看“,诚挚感谢!