天天看點

@async 預設線程池[email protected]預設線程池導緻OOM問題

位址:http://suo.im/5Y3RGF

作者:ignorewho

前言:

最近項目上在測試人員壓測過程中發現了OOM問題,項目使用springboot搭建項目工程,通過檢視日志中包含資訊:unable to create new native thread

記憶體溢出的三種類型:

1.第一種OutOfMemoryError:PermGen space,發生這種問題的原意是程式中使用了大量的jar或class 2.第二種OutOfMemoryError:Java heap space,發生這種問題的原因是java虛拟機建立的對象太多 3.第三種OutOfMemoryError:unable to create new native thread,建立線程數量太多,占用記憶體過大 初步分析: 初步懷疑是線程建立太多導緻,使用jstack 線程号 > /tmp/oom.log将應用的線程資訊列印出來。檢視oom.log,發現大量線程處于Runnable狀态,基本可以确認是線程建立太多了。

代碼分析:

1.出問題的微服務是日志寫庫服務,對比日志,鎖定在writeLog方法上,wirteLog方法使用[email protected]注解,寫庫操作采用的是異步寫入方式。 2.之前沒有對@Async注解深入研究過,隻是知道可以自定義内部線程池,經檢視,日志寫庫服務并未自定義異步配置,使用的是[email protected]預設異步配置 3.首先簡單百度了下,網上提到@Async預設異步配置使用的是SimpleAsyncTaskExecutor,該線程池預設來一個任務建立一個線程,在壓測情況下,會有大量寫庫請求進入日志寫庫服務,這時就會不斷建立大量線程,極有可能壓爆伺服器記憶體。 借此機會也學習了下SimpleAsyncTaskExecutor源碼,總結如下: 1.SimpleAsyncTaskExecutor提供了限流機制,通過concurrencyLimit屬性來控制開關,當concurrencyLimit>=0時開啟限流機制,預設關閉限流機制即concurrencyLimit=-1,當關閉情況下,會不斷建立新的線程來處理任務,核心代碼如下:

@async 預設線程池[email protected]預設線程池導緻OOM問題

2.SimpleAsyncTaskExecutor限流實作 首先任務進來,會循環判斷目前執行線程數是否超過concurrencyLimit,如果超了,則目前線程調用wait方法,釋放monitor對象鎖,進入等待

@async 預設線程池[email protected]預設線程池導緻OOM問題

線程任務執行完畢後,目前執行線程數會減一,會調用monitor對象的notify方法,喚醒等待狀态下的線程,等待狀态下的線程會競争monitor鎖,競争到,會繼續執行線程任務。

@async 預設線程池[email protected]預設線程池導緻OOM問題

雖然看了源碼了解了SimpleAsyncTaskExecutor有限流機制,實踐出真知,我們還是測試下:

一、測試未開啟限流機制下,我們啟動20個線程去調用異步方法,檢視Java VisualVM工具如下

@async 預設線程池[email protected]預設線程池導緻OOM問題

二、測試開啟限流機制,開啟限流機制的代碼如下:

@async 預設線程池[email protected]預設線程池導緻OOM問題

同樣,我們啟動20個線程去調用異步方法,檢視Java VisualVM工具如下:

@async 預設線程池[email protected]預設線程池導緻OOM問題

通過上面驗證可知:

1.開啟限流情況下,能有效控制應用線程數

2.雖然可以有效控制線程數,但執行效率會降低,會出現主線程等待,線程競争的情況。

3.限流機制适用于任務處理比較快的場景,對于應用處理時間比較慢的場景并不适用。==

最終解決辦法:

1.自定義線程池,使用LinkedBlockingQueue阻塞隊列來限定線程池的上限

2.定義拒絕政策,如果隊列滿了,則拒絕處理該任務,列印日志,代碼如下:

@async 預設線程池[email protected]預設線程池導緻OOM問題

往期推薦

對MySQL的鎖了解嗎

Kafka 事務之偏移量的送出對資料的影響

Spring 最常用的 7 大類注解

中國網際網路史就是一部流氓史!

如何幹掉惡心的 SQL 注入?

Linux 最常用指令

繼續閱讀