java.lang.OutOfMemoryError- unable to create new native thread 問題排查

bjehp發表於2023-04-13

問題描述

最近連續兩天大約凌晨3點,線上服務開始異常,出現OOM報錯。且服務所在的物理機只能ping通,但是無法登入。報錯資訊如下:

ERROR 04-12 03:01:43,930 [DefaultQuartzScheduler_Worker-3] JobRunShell[JobRunShell]:211 Job threw an unhandled Exception:
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 java.util.concurrent.ForkJoinPool.createWorker(ForkJoinPool.java:1483)
        ...
        at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
        at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)

排查過程

根據日誌OOM報錯,懷疑是記憶體不足或記憶體洩露的原因,需要檢視記憶體的使用情況。考慮到JConsoleVisualVM具有視覺化介面,能看出歷史變化趨勢,更直觀地排查問題,因此為程式配置了jmx引數。重啟應用,使用VisualVM連線應用的jmx埠。應用配置jmx埠的引數如下:

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false  -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=9998

VisualVM顯示使用的記憶體是正常的,沒有持續飆升。但是執行緒數卻比較異常,隨時間推移在持續增加。因此考慮看看是什麼執行緒在持續增加,jstack是一個比較好用的工具,它用於生成 JAVA 虛擬機器當前時刻的執行緒快照。

java.lang.OutOfMemoryError- unable to create new native thread 問題排查

使用jstack -F PID列印出了所有執行緒,發現有大片下面的堆疊資訊。注意到com.xxx.http.IdleConnectionMonitorThread.run() @bci=15, line=22 (Compiled frame)方法出現的特別多。

Thread 7760: (state = BLOCKED)
 - sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
 - java.util.concurrent.ForkJoinPool.awaitWork(java.util.concurrent.ForkJoinPool$WorkQueue, int) @bci=354, line=1821 (Compiled frame)
 - java.util.concurrent.ForkJoinPool.runWorker(java.util.concurrent.ForkJoinPool$WorkQueue) @bci=44, line=1690 (Compiled frame)
 - java.util.concurrent.ForkJoinWorkerThread.run() @bci=24, line=157 (Compiled frame)


Thread 7759: (state = BLOCKED)
 - java.lang.Object.wait(long) @bci=0 (Compiled frame; information may be imprecise)
 - com.xxx.http.IdleConnectionMonitorThread.run() @bci=15, line=22 (Compiled frame)
 - java.lang.Thread.run() @bci=11, line=745 (Compiled frame)


Thread 7758: (state = BLOCKED)
 - java.lang.Object.wait(long) @bci=0 (Compiled frame; information may be imprecise)
 - com.xxx.http.IdleConnectionMonitorThread.run() @bci=15, line=22 (Compiled frame)
 - java.lang.Thread.run() @bci=11, line=745 (Compiled frame)

在專案搜尋類IdleConnectionMonitorThread,並檢視22行內容。發現如果不執行shutdown()方法,那麼該後臺執行緒會持續地執行wait()方法,導致該執行緒不退出。實際情況是確實沒有執行shutdown()方法,隨著每10分鐘執行一次計算任務,每次計算任務會執行一批http請求,每個http請求就會建立出一個後臺執行緒,這樣會導致執行緒數越來越多。

java.lang.OutOfMemoryError- unable to create new native thread 問題排查

聯絡了sdk的提供方,提供了後臺程式的停止方法。在程式請求http完成後,釋放請求的資源,停止了後臺執行緒。使用VisualVm觀察了一段時間,發現執行緒數不在增長了,未出現OOM報錯。至此問題解決。

java.lang.OutOfMemoryError- unable to create new native thread 問題排查

雖然問題解決了,不過還有個疑問,執行緒數達到多少會觸發unable to create new native thread報錯。查閱部落格瞭解到,一個程式最多能建立多少執行緒是受多因素影響的,基本上是系統支援的最大PID、使用者可建立最大執行緒數、系統支援的最大執行緒數的最小值。透過檢視伺服器配置,系統支援的最大PID的值是最小的:32768,也就是說一個程式最多建立大約3w個執行緒。由於伺服器上部署了線上服務,不方便在復現驗證建立多少個執行緒時出現OOM報錯。

java.lang.OutOfMemoryError: unable to create new native thread問題排查以及當前系統最大程式數量

一個JVM可以建立多少執行緒,首先由JVM設定決定(-Xms,-Xmx,-Xss),另外受到外部因素影響,就是系統設定(最大PID、最大執行緒、棧記憶體大小、最重要的還是實體記憶體由多少)、其二就是使用者設定(使用者可以執行多少個程式或執行緒),綜合上述因素的最小值就是一個JVM可以建立多少執行緒。

系統支援的最大程式數
cat /proc/sys/kernel/pid_max
32768

系統支援的最大執行緒數
cat /proc/sys/kernel/threads-max
513024

程式可用最大虛擬記憶體
ulimit -v
unlimited

最大棧大小
ulimit -s
8192

每個使用者可建立最大程式數
ulimit -u
256512

總結

1.JConsoleVisualVM具有視覺化介面,可以方便地檢視CPU佔用、記憶體使用、類數量、執行緒數的歷史變化趨勢。

2.jstack命令可以檢視正在執行的程式當前時刻的所有執行緒資訊。

3.一個程式最多能建立多少執行緒,是受多因素影響的,基本上是系統支援的最大PID、使用者可建立最大執行緒數、系統支援的最大執行緒數的最小值。

參考資料

相關文章