最近线上出现了一个OOM的问题,使得服务异常以至于不可用。
一、现象
从现象来看就是请求服务全部失败,线程数激增,cpu和内存显示相对正常。查看error.log,都是"error.log:unable to create new native thread"错误:
[ERROR] [org.springframework.boot.web.servlet.support.ErrorPageFilter:190] -- Forwarding to error page from request [/a/b/c/d/e/f] due to exception [unable to create new native thread]
java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:714)
at sun.net.www.http.KeepAliveCache$1.run(KeepAliveCache.java:112)
at sun.net.www.http.KeepAliveCache$1.run(KeepAliveCache.java:96)
at java.security.AccessController.doPrivileged(Native Method)
at sun.net.www.http.KeepAliveCache.put(KeepAliveCache.java:95)
at sun.net.www.http.HttpClient.putInKeepAliveCache(HttpClient.java:407)
at sun.net.www.http.HttpClient.finished(HttpClient.java:364)
at sun.net.www.http.ChunkedInputStream.closeUnderlying(ChunkedInputStream.java:219)
at sun.net.www.http.ChunkedInputStream.processRaw(ChunkedInputStream.java:455)
at sun.net.www.http.ChunkedInputStream.readAheadNonBlocking(ChunkedInputStream.java:520)
at sun.net.www.http.ChunkedInputStream.readAhead(ChunkedInputStream.java:611)
at sun.net.www.http.ChunkedInputStream.available(ChunkedInputStream.java:725)
at java.io.FilterInputStream.available(FilterInputStream.java:168)
很明显是说无法创建本地线程了。
二、分析
通过jstack来进行分析:
EndpointManagerSD update schedule : #4077 daemon prio=5 os_prio=5 tid=0x000007exxxx nid=xxxxx waiting on condition (xxxxxx)
java.lang.Thread.State: TIMED_WAITING(parking)
at sun.misc.Unsafe.park(Native method)
- parking to wait for <0x000000000xafa> (a java.util.concurrent.AbstractQueuedSynchronizer.java:xxxx)
at java.util.concurrent.locks.LockSupport.parkNanos
at
发现大量线程处于:TIME_WAITING阶段,根据关键字可以定位到代码中的问题。
原因就是在生产的过程中一直在创建消息队列的producer客户端,producer.start()之后没有close。
public void sendMsg(String msg) {
Producer producer = new Procuer();
producer.send(msg);
}
producer是我们发送消费的客户端,包含一定的资源,如线程,网络连接等,使用前进行初始化,使用完后,shutdown释放资源。在我们的使用中,每一条消息创建一个producer,但是使用完后,没有shutdown释放资源,那么随着不断new Producer,就会不断创建新的线程而不释放。推荐使用方式是,服务启动初始化producer,服务关闭时,释放资源,中间保持长连接复用。
该问题发生的常见过程主要包括以下几步:
1.JVM 内部的应用程序请求创建一个新的 Java 线程;
2.JVM native方法代理了该次请求,并向操作系统请求创建一个 native 线程;
3.操作系统尝试创建一个新的native 线程,并为其分配内存;
4.如果操作系统的虚拟内存已耗尽,或是受到 32 位进程的地址空间限制,操作系统就会拒绝本次native 内存分配;
5.JVM将抛出 java.lang.OutOfMemoryError: Unable to create new native thread 错误。
能创建的线程数的具体计算公式如下:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
MaxProcessMemory:指的是一个进程的最大内存
JVMMemory:JVM内存
ReservedOsMemory :保留的操作系统内存
ThreadStackSize:线程栈的大小
三、解决方案
1. 调用完之后将producer.close()。
public void sendMsg(String msg) {
Producer producer = new Procuer();
producer.send(msg);
producer.close();
}
2.维持一个维持producer复用对象,使用ConcurrentHashMap来保存topic和producer引用。
3.通用方案:
a.如果程序中有bug,导致创建大量不需要的线程或者线程没有及时回收,那么必须解决这个bug,修改参数是不能解决问题的。
b.如果程序确实需要大量的线程,现有的设置不能达到要求,那么可以通过修改MaxProcessMemory,JVMMemory,ThreadStackSize这三个因素,来增加能创建的线程数.
c.适当牺牲性能,优化代码里面的线程池的线程配置。
d.升级配置,为机器提供更多的内存;
e.降低Java Heap Space大小;
f.修复应用程序的线程泄漏问题;
g.限制线程池大小;
h.使用 -Xss 参数减少线程栈的大小;
i.调高 OS 层面的线程最大数:执行 ulimia -a 查看最大线程数限制,使用 ulimit -u xxx 调整最大线程数限制。
Author:忆之独秀
Email:leaguenew@qq.com
转载注明出处:https://blog.csdn.net/lavorange/article/details/103448504