GC 為何會導致執行緒數降低?
疑惑
近期收到一些報警,是方法效能報警,定位原因主要是瞬時流量突增引起的,但是觀察方法中查詢 MySQL 的效能不差,效能花費在哪裡?同時觀察 JVM 效能,發現 YoungGC 變多了,CPU 使用率高了,但執行緒數在當時刻降低了,YoungGC 會建立新執行緒進行垃圾回收,應該執行緒數增多,為什麼會降低的?
解惑
效能為什麼慢了?
由於提供服務的 API 所依賴的 MySQL 效能並不慢,那原因是什麼?唯一的問題點就是流量,流量突增引發系統開啟大量執行緒進行處理,執行緒狀態處於 Running 或 Runable 狀態,這時作業系統(分時作業系統)會讓 CPU 進行分時處理,從而引發 CPU 使用率上升。多執行緒併發處理會讓伺服器處理速度變快,但對於單個服務響應會變慢。
還有我們一般的系統架構部署多是 Nginx - Tomcat,而 Tomcat 預設執行緒數多是1000上限,且Tomcat 7以下版本預設是 BIO 模式,超過上限的執行緒會在 Tomcat 阻塞佇列裡等待,所以對於呼叫方來說,感知的時間會更久一些。
執行緒數為什麼減少?
我們檢視 JVM 的是配置內容,Young GC 是 PS Scavenge,Full GC 是 PS MarkSweep。
並行(Parallel)指多條 GC 執行緒並行工作,但此時使用者執行緒處於等待狀態。併發(Concurrent)指使用者執行緒與 GC 執行緒同時執行(不一定是並行的,可能會是交替執行);使用者程式執行緒繼續執行,而垃圾收集執行緒運行於另一個 CPU 上。
所以,Young GC 是 PS Scavenge,在 GC 時會出現 stop-the-world 的情況,此時使用者執行緒處於阻塞情況,所以瞬時執行緒數會降低,當 GC 結束後,使用者執行緒會恢復執行,所以又會上去。
為什麼會發生這麼多 Young GC?
Young GC 是對 Young Generation 的垃圾收集,YGCeden 空間不足會進行 Eden 和 Survivor 的 YGC。一個執行緒會在記憶體建立一個堆疊的空間,多執行緒會頻繁的建立和釋放記憶體空間,所以 YGCeden 會進行頻繁的垃圾回收,因此會發生這麼多 YGC。
我們知道 JVM 大多數採用主動式中斷,即 GC 需要中斷執行緒的時候,它就設個標誌位(safe-point),執行執行緒會主動輪詢這個標誌位,如果標誌位就緒的話就自行中斷。
堆記憶體為什麼減少了?
我們觀察到堆記憶體的使用情況也降低了。
這是因為 PS MarkSweep 是標記-清除演算法,PS Scavenge 是複製演算法,GC 回收是進行記憶體無引用的記憶體回收,所以會讓對記憶體佔用減少。
思考
考慮現有系統的應用場景,如果是網關係統,頻繁的 Young GC 和 Full GC 在 stop-the-world 發生時,一定會影響呼叫方請求,極端情況下出現 502 的問題。所以,在設計系統的時候,還是要儘量減少 Young GC 和 Full GC。