故障排除Unable to Create New Native Thread

语言: CN / TW / HK

高并发场景下经常会出现 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、资源占用、延迟,如果超过要进行限流或者扩容(性能不够,机器来凑),以及上线后的监控告警,通过这种方式可以从系统层面杜绝此类异常出现。

推荐

A Big Picture of Kubernetes

Kubernetes入门培训(内含PPT)

原创不易,随手关注或者”在看“,诚挚感谢!