最近連續兩天大約凌晨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報錯,懷疑是記憶體不足或記憶體洩露的原因,需要檢視記憶體的使用情況。考慮到JConsole
或VisualVM
具有視覺化介面,能看出歷史變化趨勢,更直觀地排查問題,因此為程式設定了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 虛擬機器器當前時刻的執行緒快照。
使用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請求就會建立出一個後臺執行緒,這樣會導致執行緒數越來越多。
聯絡了sdk的提供方,提供了後臺程序的停止方法。在程式請求http完成後,釋放請求的資源,停止了後臺執行緒。使用VisualVm
觀察了一段時間,發現執行緒數不在增長了,未出現OOM報錯。至此問題解決。
雖然問題解決了,不過還有個疑問,執行緒數達到多少會觸發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.JConsole
或VisualVM
具有視覺化介面,可以方便地檢視CPU佔用、記憶體使用、類數量、執行緒數的歷史變化趨勢。
2.jstack
命令可以檢視正在執行的程式當前時刻的所有執行緒資訊。
3.一個程序最多能建立多少執行緒,是受多因素影響的,基本上是系統支援的最大PID、使用者可建立最大執行緒數、系統支援的最大執行緒數的最小值。