最近線上出現了一個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:[email protected]
轉載註明出處:https://blog.csdn.net/lavorange/article/details/103448504