故障排除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限制,具体可以参考: http://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 的空间。有人给出了一个根据堆外内存计算线程大小的公式: http://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、资源占用、延迟,如果超过要进行限流或者扩容(性能不够,机器来凑),以及上线后的监控告警,通过这种方式可以从系统层面杜绝此类异常出现。
推荐
原创不易,随手关注或者”在看“,诚挚感谢!
- 供应链攻击:保护软件供应链的 6 个步骤
- Docker 安全最佳实践和备忘单
- 2021 OWASP Top10 有什么新变化?
- 解决 K8s 落地难题的方法论提炼
- 我们如何通过升级文件系统节省数百万 SSD 成本
- 为什么云中的容器可以成为攻击者的天堂
- 如何诊断内存泄漏|有趣的垃圾回收
- Java容器化参数配置最佳实践
- DNS 故障诊断及问题分析示例
- 我是如何完成从 Scala 到 Go 过渡的?
- 为什么以及如何从命令行使用 containerd
- Golang标准库和外部库的性能对比
- 什么是标准容器(2021 版)
- 限制K8S Pod 磁盘容量使用的 3 种方法
- 基于网络抓包实现K8S中微服务的应用级监控
- 单元测试最佳实践|如何避免常见陷阱?
- Chaos Mesh 如何助力 Apache APISIX 提高系统稳定性
- 星巴克不使用两阶段提交
- 关于服务兼容性设计一点思考
- BeyondProd:云原生安全的一种新方法