故障排除Unable to Create New Native Thread

高并发场景下经常会出现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)


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

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值