如何快速定位並最佳化CPU與JVM記憶體效能瓶頸?

阿里妹導讀
本文介紹了 Java 應用常見的 CPU & JVM 記憶體熱點原因及最佳化思路。
雙十一大促前夕,部門組織了核心應用全鏈路壓測,你負責的訂單中心在第一波壓測流量脈衝下 CPU 利用率瞬間飆升到 95% 以上,介面呼叫大量超時,成為全鏈路卡點,最終導致壓測活動草草結束,主管責令限期1天解決,該如何快速定位 CPU 效能瓶頸完成最佳化?
熬夜爆肝寫了2千行程式碼,終於趕在專案截止日期前完成線上釋出,沒等你美美的喝完一瓶冰可樂,手機就開始滴滴的響個不停,告警電話如雨後春筍般接踵而至,JVM 記憶體持續 FGC,請求超時流量下跌,面對領導和客戶的催促,該如何快速定位記憶體效能瓶頸解決風險?
以上場景對於參與 Java 應用研發或運維的同學來說,相信都不陌生。CPU 和 JVM 記憶體是 Java 應用的核心資源,一旦出現熱點導致資源不足,很容易引發大面積故障。因此,掌握高效的 CPU 與 JVM 記憶體效能最佳化手段就顯得尤為重要。
CPU 效能最佳化實戰
CPU(Central Processing Unit)是計算機系統的運算和控制核心,是資訊處理、程式執行的最終執行單元,相當於系統的“大腦”。當 CPU 過於繁忙,就像“人腦”併發處理過多的事情,會降低做事的效率,嚴重時甚至會導致崩潰“宕機”。因此,合理控制 CPU 的負載,是保障系統穩定持續執行的重要手段。
CPU 使用率是 CPU 非空閒態執行的時間佔比,它反映了 CPU 的繁忙程度。比如,單核 CPU 1s 內非空閒態執行時間為 0.8s,那麼它的 CPU 使用率就是 80%;雙核 CPU 1s 內非空閒態執行時間分別為 0.4s 和 0.6s,那麼,總體 CPU 使用率就是 (0.4s + 0.6s) / (1s * 2) = 50%,其中 2 表示 CPU 核數,多核 CPU 同理。根據經驗法則, 建議生產系統的 CPU 總使用率不要超過 70%。
CPU 使用率只反映系統健康狀態的度量指標,並不是問題的根因。因此,它的價值主要體現在兩個方面: 一是綜合反映當前系統的健康程度,結合監控告警產品,實現快速響應;二是初步定界問題方向,縮小排查範圍,降低故障恢復時間。 比如當 CPU iowait 高時,應優先排查磁碟 I/O;當 CPU steal 高時,就優先排查宿主機狀態。CPU 涵蓋的問題場景有很多,限於篇幅限制,下面以最常見的使用者態 CPU 使用率高為例,介紹下 Java 應用的排查思路。
如何排查使用者態 CPU 使用率高?
使用者態 CPU 使用率反映了應用程式的繁忙程度,通常與我們自己寫的程式碼息息相關。因此,當你在做應用釋出、配置變更或效能最佳化時,如果想定位消耗 CPU 最多的 Java 程式碼,可以遵循如下思路:
  • 透過 top 命令找到 CPU 消耗最多的程序號;
  • 透過 top -Hp 程序號 命令找到 CPU 消耗最多的執行緒號(列名仍然為 PID);
  • 透過 printf "%x\n" 執行緒號 命令輸出該執行緒號對應的 16 進位制數字;
  • 透過 jstack 程序號 | grep 16進位制執行緒號 -A 10 命令找到 CPU 消耗最多的執行緒方法堆疊。
上述方法是目前業界常用的診斷流程,然而該方法有兩個顯著缺陷,一是操作流程複雜,而且往往一次 jstack 還不足以定位根因,需要執行多次;二是隻能用於診斷線上問題,無法記錄歷史快照,如果問題已經發生,無法復現的話,往往只能不了了之。
為了解決上述問題,業界領先的 APM 產品已經支援了常態化記錄執行緒/方法棧 CPU開銷的持續剖析能力,隨時回溯歷史快照,對比不同時段的 CPU 熱點變化。以阿里雲 ARMS 產品為例,典型的 CPU 熱點排查思路主要分為以下幾步:
1.透過主機/Pod CPU 利用率監控或告警,第一時間發現 CPU 利用率異常飆升現象。
2.透過執行緒分析監控,快速找到 CPU 消耗最高的執行緒池,比如 Pressure-CPU*。
3.透過持續剖析-CPU熱點功能,回溯任意時間段內的 CPU 佔比火焰圖,直接定位到效能瓶頸方法,比如下圖中 CPUPressure.runBusiness( ) 方法的 CPU 開銷佔比高達 99.7%,研發同學定位到具體的業務程式碼行,就可以快速最佳化程式碼解決 CPU 熱點問題。
4.生產系統的方法呼叫棧更加複雜,ARMS 還支援差分火焰圖直觀對比不同時間段的 CPU 開銷變化,比如應用釋出、大促壓測等場景,再結合 Copilot 智慧診斷給出影響 CPU 變化的關鍵方法,無需豐富的專家經驗也能輕鬆完成效能最佳化工作,如下圖所示。
JVM 記憶體效能最佳化實戰
記憶體(Memory),作為計算機系統中的關鍵組成部分,不僅影響著程式執行的速度,還決定了多工處理的能力以及資料訪問的效率。從本質上講,記憶體是一種臨時儲存介質,用於存放正在執行的程式及其相關資料,以便CPU能夠快速訪問。相比於硬碟等長期儲存裝置,記憶體具有更高的讀寫速度,但其容量相對較小且斷電後資訊會丟失。因此,在計算機體系結構中,合理配置與最佳化使用記憶體資源顯得尤為重要。
JVM 的記憶體主要分為堆(Heap)、棧(Stack)、方法區(Method Area)等幾個部分。其中,堆用於存放物件例項,而棧則儲存了方法呼叫過程中產生的區域性變數及運算元棧等資訊。方法區主要用於儲存類結構資訊如執行時常量池等。其中,堆區域是最容易產生記憶體熱點的地方,因為它直接關聯著物件生命週期管理和垃圾收集活動。當 JVM 記憶體嚴重不足時,就會丟擲 java.lang.OutOfMemoryError 錯誤,常見的 OOM 型別如下圖所示。
JVM 記憶體熱點成因分析
常見的 JVM 記憶體熱點產生原因主要包括以下幾類,每種原因背後都隱藏著複雜的機制。
1.物件建立過於頻繁:如果存在大量短生命週期的物件被頻繁地建立與銷燬,這將導致垃圾回收器(Garbage Collector, GC)頻繁工作以清理不再使用的物件空間。這種情況下,即使GC演算法本身效率很高,但由於其執行頻率過高,仍然會對系統性能造成顯著影響。例如,在迴圈體內部建立臨時變數而不進行復用。為了緩解這一問題,可以考慮使用物件池技術或儘量減少不必要的物件例項化操作。還有一種情況是上游系統請求流量飆升,常見於各類促銷/秒殺活動,此時可以考慮新增機器資源,或者做限流降級。
2.大物件分配:當應用程式中申請大物件時(如大型陣列),通常會被直接分配到老年代而非新生代區域。雖然這樣做可以避免短期內因這些大物件而觸發 YoungGC,但如果此類物件數量較多,則可能會迅速填滿老年代空間,進而迫使Full GC發生。Full GC會暫停所有使用者執行緒並掃描整個堆區,因此對應用效能的影響尤為嚴重。針對這種情況,建議評估是否真的需要如此大的資料結構,並探索更高效的資料表示方式。
3.記憶體洩漏:儘管Java具有自動記憶體管理功能,但不當的設計模式或程式設計習慣仍可能導致記憶體洩露問題。比如,靜態集合類持有外部引用、未關閉的資料庫連線等都是常見場景。隨著時間推移,這些無法被正常回收的物件逐漸積累起來,最終耗盡可用堆空間。解決之道,首先透過一些監控分析工具定界不斷增長的記憶體位置來源,判斷記憶體洩露是發生在堆內還是堆外,如果是堆內可以藉助諸如jmap等工具下載記憶體快照,檢查堆內佔比高的記憶體物件,並結合程式碼分析根因。如果是堆外部分出現了記憶體穩定增長,此時需要藉助一些外部診斷工具,比如 NMT(Native Memory Tracking)等對堆外記憶體申請情況進行監測,分析可能的原因。
4.不合理的堆大小設定:JVM啟動引數中的-Xms(初始堆大小)和-Xmx(最大堆大小)對於控制記憶體使用至關重要。如果這兩個值設定得過低,則可能因為頻繁的GC活動而降低程式效能;反之,若設定得過高,則又會浪費寶貴的物理記憶體資源。理想狀態下,應根據實際業務需求及硬體配置情況合理調整這兩個引數,一般設定為總記憶體大小的1/2左右,然後留1/2給非堆部分使用。此外,-XX:NewRatio等選項的設定也很重要,需要基於其去平衡新生代與老年代的比例關係,從而達到最佳效能狀態。
5.載入的 class 數目太多或體積太大:永久代(Permanent Generation,JDK 1.8 使用 Metaspace 替換)的使用量與載入到記憶體的 class 的數量/大小正相關。當載入的 class 數目太多或體積太大時,會導致 永久代用滿,從而導致記憶體溢位報錯。可以透過 -XX:MaxMetaspaceSize / -XX:MaxPermSize 上調永久代大小。
如何排查 JVM 記憶體熱點問題?
生產環境需要常態化跟蹤 JVM 記憶體變化,如何第一時間發現 JVM 記憶體問題,並快速定位止血,整體思路與 CPU 熱點最佳化類似,主要包括以下步驟:
1.透過 JVM 監控/告警發現記憶體或 GC 異常,分析新生代、老年代、Metaspace、DirectBuffer 等記憶體變化。
2.透過持續剖析-記憶體熱點功能,常態化記錄每個方法的記憶體物件分配佔比火焰圖,比如下圖中AllocMemoryAction.runBusiness() 方法消耗了 99.92% 的記憶體物件分配。
3.記憶體快照記錄了相關時刻的堆記憶體物件佔用和程序類載入等資訊。阿里雲 ARMS 提供了一種開箱即用的記憶體快照白屏化操作功能,讓快照建立、獲取和分析更加簡單便捷。結合阿里雲 ATP 分析工具,實現了 JVM 記憶體物件與引用關係的深入分析和診斷。
小結
本文介紹了 Java 應用常見的 CPU & JVM 記憶體熱點原因及最佳化思路,首先透過監控告警及時發現資源使用率的異動,然後結合方法級別的 CPU/記憶體火焰圖定位熱點程式碼,幫忙研發同學快速排障,最佳化系統資源使用,確保應用在高負載下的穩定執行。
基於快取實現應用提速
隨著業務發展,承載業務的應用將會面臨更大的流量壓力,如何降低系統的響應時間,提升系統性能成為了每一位開發人員需要面臨的問題,使用快取是首選方案。本方案介紹如何運用雲資料庫Redis版構建快取為應用提速。   
點選閱讀原文檢視詳情。

相關文章