記錄一次因爲第三方工具使用不當引發的服務器內存被耗盡,導致Java服務無法創建新線程的OOM,當時太忙沒有抽出時間來記錄,現在只能憑藉記憶和其他文章來還原當時問題的排查流程日後備用。
問題發現:
這個問題是在開發新需求時,在測試環境被暴露出來的,測試反饋說所有的接口突然都調不通了。
解決流程:
1、接口報異常連上服務器tail日誌,這個是當時日誌報出的異常現場截圖:(異常描述的清晰明瞭:無法創建新的本機線程)
2、通過對新提交的代碼Review並沒有發現可能出現問題的地方(後來發現確實是代碼的問題)。
3、代碼沒發現問題的話,試着修改了Linux系統關於JVM的配置如:-Xss、-Xms、-Xmx等,然並卵。
-Xss 128k:設置每個線程的堆棧大小。JDK5.0以後每個線程堆 棧大小爲1M,以前每個線程堆棧大小爲256K。在相同物理內存下,Xss越大,每個線程的大小就越大,佔用的內存越多,能容納的線程就越少;Xss越小,則遞歸的深度越小,容易出現棧溢出 java.lang.StackOverflowError。。但是操作系統對一 個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。線程棧的大小是個雙刃劍,如果設置過小,可能會出現棧溢出,特別是在該線程內有遞歸、大的循環時出現溢出的可能性更大,如果該值設置過大,就有影響到創建棧的數量,如果是多線程的應用,就會出現內存溢出的錯誤。
4、幾番周測,在整理思路後,決定首要任務就是如何重現該問題,於是編寫測試程序,測試出操作系統最大能夠創建的線程數:
import java.util.concurrent.CountDownLatch;
public class TestNativeOutOfMemoryError {
public static void main(String[] args) {
for (int i = 0;; i++) {
System.out.println("i = " + i);
new Thread(new HoldThread()).start();
}
}
}
class HoldThread extends Thread {
CountDownLatch cdl = new CountDownLatch(1);
public HoldThread() {
this.setDaemon(true);
}
public void run() {
try {
cdl.await();
} catch (InterruptedException e) {
}
}
}
運行後:
i = 1002
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:597)
at TestNativeOutOfMemoryError.main(TestNativeOutOfMemoryError.java:20)
問題重現,在反覆運行幾次後發現,生產系統最大隻能創建1002多個線程。而我本地PC電腦都可以創建2500左右。
操作系統64位CentOS release 6.7 (Final),java version "1.8.0_101",64G內存。
感覺原因快找到了,切換到運行賬戶使用命令:
生產上所有程序都是在jenkins賬戶下運行,於是查看該賬戶下所有的線程數總和爲908,也即是說,隨時都可能會超過1002,導致內存溢出。查看看當前運行的線程數命令爲:
[jenkins@localhost ~]$ ps -eLf | wc -l
原因找到,操作系統對運行程序的賬戶有最大線程數限制。
[root@localhost ~]# cat /etc/security/limits.d/90-nproc.conf
打開後發現除了root,其他賬戶都限制在8096個。
於是增加一條:jenkins soft nproc 20000
爲什麼設置爲20000,因爲測試後發現,在運行到35000左右,系統就報內存溢出了,操作系統所有命令都不能使用,因此將程序最大線程數限制在20000。
修改後問題未解決(因爲問題不是出現在OS上),但是操作系統可創建的線程數增加了很多。
總結:
一:修改JVM的-Xss參數,減小新創建線程的內存佔用,相同的物理內存下可以創建更多的線程。
二:修改/etc/security/limits.d/90-nproc.conf中關於操作系統對用戶最大線程數的限制
備註:
Apache的HttpClient據說很強大,這次的原因是HttpClient的資源釋放和超時處理的配置不當,導致每次請求都會新建線程,並且新建的線程不會被回收,最終將服務器的內存耗盡。關於HttpClient的這個問題可以參考這篇文章
文章參考:
https://www.cnblogs.com/myshare/archive/2016/02/02/5177135.html
https://blog.csdn.net/penghaiping1001/article/details/73199300