公司來了個大佬,把 FullGC 40 次/天優化為 10 天 1 次,太秀了~!

来源:https://www.cnblogs.com/javastack/archive/2023/08/25/17656059.html
-Advertisement-
Play Games

來源:https://heapdump.cn/article/1859160 通過這一個多月的努力,將 FullGC 從 40 次/天優化到近 10 天才觸發一次,而且 YoungGC 的時間也減少了一半以上,這麼大的優化,有必要記錄一下中間的調優過程。 對於 JVM 垃圾回收,之前一直都是處於理論 ...


來源:https://heapdump.cn/article/1859160

通過這一個多月的努力,將 FullGC 從 40 次/天優化到近 10 天才觸發一次,而且 YoungGC 的時間也減少了一半以上,這麼大的優化,有必要記錄一下中間的調優過程。

對於 JVM 垃圾回收,之前一直都是處於理論階段,就知道新生代,老年代的晉升關係,這些知識僅夠應付面試使用的。

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

問題

前一段時間,線上伺服器的 FullGC 非常頻繁,平均一天 40 多次,而且隔幾天就有伺服器自動重啟了,這表明伺服器的狀態已經非常不正常了,得到這麼好的機會,當然要主動請求進行調優了。

未調優前的伺服器 GC 數據,FullGC 非常頻繁。

首先伺服器的配置非常一般(2 核 4G),總共 4 台伺服器集群。每台伺服器的 FullGC 次數和時間基本差不多。其中 JVM 幾個核心的啟動參數為:

-Xms1000M -Xmx1800M -Xmn350M -Xss300K -XX:+DisableExplicitGC -XX:SurvivorRatio=4 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC
  • -Xmx1800M:設置 JVM 最大可用記憶體為 1800M。
  • -Xms1000m:設置 JVM 初始化記憶體為 1000m。此值可以設置與 -Xmx 相同,以避免每次垃圾回收完成後 JVM 重新分配記憶體。
  • -Xmn350M:設置年輕代大小為 350M。整個 JVM 記憶體大小 = 年輕代大小 + 年老代大小。增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun 官方推薦配置為整個堆的 3/8。
  • -Xss300K:設置每個線程的堆棧大小。JDK5.0 以後每個線程堆棧大小為 1M,以前每個線程堆棧大小為 256K。根據應用的線程所需記憶體大小進行調整。在相同物理記憶體下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在 3000~5000 左右。

第一次優化

一看參數,馬上覺得新生代為什麼這麼小,這麼小的話怎麼提高吞吐量,而且會導致 YoungGC 的頻繁觸發,如上圖的新生代收集就耗時 830s。

初始化堆記憶體沒有和最大堆記憶體一致,查閱了各種資料都是推薦這兩個值設置一樣的,可以防止在每次 GC 後進行記憶體重新分配。

基於前面的知識,於是進行了第一次的線上調優:提升新生代大小,將初始化堆記憶體設置為最大記憶體。

-Xmn350M -> -Xmn800M
-XX:SurvivorRatio=4 -> -XX:SurvivorRatio=8
-Xms1000m ->-Xms1800m

將 SurvivorRatio 修改為 8 的本意是想讓垃圾在新生代時儘可能的多被回收掉。

就這樣將配置部署到線上兩台伺服器(prod,prod2 另外兩台不變方便對比)上後,運行了 5 天後,觀察 GC 結果,YoungGC 減少了一半以上的次數,時間減少了 400s,但是 FullGC 的平均次數增加了 41 次。YoungGC 基本符合預期設想,但是這個 FullGC 就完全不行了。

就這樣第一次優化宣告失敗。

第二次優化

在優化的過程中,我們的主管發現了有個對象 T 在記憶體中有一萬多個實例,而且這些實例占據了將近 20M 的記憶體。於是根據這個 bean 對象的使用,在項目中找到了原因:匿名內部類引用導致的,偽代碼如下:

public void doSmthing(T t){
 redis.addListener(new Listener(){
  public void onTimeout(){
   if(t.success()){
    //執行操作
   }
  }
 });
}

由於 listener 在回調後不會進行釋放,而且回調是個超時的操作,當某個事件超過了設定的時間(1 分鐘)後才會進行回調,這樣就導致了 T 這個對象始終無法回收,所以記憶體中會存在這麼多對象實例。

通過上述的例子發現了存在記憶體泄漏後,首先對程式中的 error log 文件進行排查,首先先解決掉所有的 error 事件。然後再次發佈後,GC 操作還是基本不變,雖然解決了一點記憶體泄漏問題,但是可以說明沒有解決根本原因,伺服器還是繼續莫名的重啟。

記憶體泄漏調查

經過了第一次的調優後發現記憶體泄漏的問題,於是大家都開始將進行記憶體泄漏的調查,首先排查代碼,不過這種效率是蠻低的,基本沒發現問題。於是線上上不是很繁忙的時候繼續進行 dump 記憶體,終於抓到了一個大對象。

這個對象竟然有 4W 多個,而且都是清一色的 ByteArrowRow 對象,可以確認這些數據是資料庫查詢或者插入時產生的了。

於是又進行一輪代碼分析,在代碼分析的過程中,通過運維的同事發現了在一天的某個時候入口流量翻了好幾倍,竟然高達 83MB/s ,經過一番確認,目前完全沒有這麼大的業務量,而且也不存在文件上傳的功能。

咨詢了阿裡雲客服也說明完全是正常的流量,可以排除攻擊的可能。

就在我還在調查入口流量的問題時,另外一個同事找到了根本的原因,原來是在某個條件下,會查詢表中所有未處理的指定數據,但是由於查詢的時候 where 條件中少加了模塊這個條件,導致查詢出的數量達 40 多萬條,而且通過 log 查看當時的請求和數據,可以判斷這個邏輯確實是已經執行了的,dump 出的記憶體中只有 4W 多個對象,這個是因為 dump 時候剛好查詢出了這麼多個,剩下的還在傳輸中導致的。而且這也能非常好的解釋了為什麼伺服器會自動重啟的原因。

解決了這個問題後,線上伺服器運行完全正常了,使用未調優前的參數,運行了 3 天左右 FullGC 只有 5 次。

第二次調優

記憶體泄漏的問題已經解決了,剩下的就可以繼續調優了,經過查看 GC log,發現前三次 FullGC 時,老年代占據的記憶體還不足 30%,卻發生了 FullGC。

於是進行各種資料的調查,在 https://blog.csdn.net/zjwstz/article/details/77478054博客中非常清晰明瞭的說明 metaspace 導致 FullGC 的情況,伺服器預設的 metaspace 是 21M,在GC log中看到了最大的時候 metaspace 占據了 200M 左右,於是進行如下調優,以下分別為 prod1 和 prod2 的修改參數,prod3,prod4 保持不變。

-Xmn350M -> -Xmn800M
-Xms1000M ->1800M
-XX:MetaspaceSize=200M
-XX:CMSInitiatingOccupancyFraction=75

-Xmn350M -> -Xmn600M
-Xms1000M ->1800M
-XX:MetaspaceSize=200M
-XX:CMSInitiatingOccupancyFraction=75

prod1 和 2 只是新生代大小不一樣而已,其他的都一致。到線上運行了 10 天左右,進行對比:

prod1:

prod2:

prod3:

prod4:

對比來說,1,2 兩台伺服器 FullGC 遠遠低於 3,4 兩台,而且 1,2 兩台伺服器的 YounGC 對比 3,4 也減少了一半左右。

而且第一臺伺服器效率更為明顯,除了 YoungGC 次數減少,而且吞吐量比多運行了一天的 3,4 兩台的都要多(通過線程啟動數量),說明 prod1 的吞吐量提升尤為明顯。

通過 GC 的次數和 GC 的時間,本次優化宣告成功,且 prod1 的配置更優,極大提升了伺服器的吞吐量和降低了 GC 一半以上的時間。

prod1 中的唯一一次 FullGC:

通過 GC log 上也沒看出原因,老年代在 cms remark 的時候只占據了 660M 左右,這個應該還不到觸發 FullGC 的條件,而且通過前幾次的 YoungGC 調查,也排除了晉升了大記憶體對象的可能,通過 metaspace 的大小,也沒有達到 GC 的條件。這個還需要繼續調查,有知道的歡迎指出下,這裡先行謝過了。

總結

通過這一個多月的調優總結出以下幾點:

  • FullGC 一天超過一次肯定就不正常了。
  • 發現 FullGC 頻繁的時候優先調查記憶體泄漏問題。
  • 記憶體泄漏解決後,jvm 可以調優的空間就比較少了,作為學習還可以,否則不要投入太多的時間。
  • 如果發現 CPU 持續偏高,排除代碼問題後可以找運維咨詢下阿裡雲客服,這次調查過程中就發現 CPU 100% 是由於伺服器問題導致的,進行伺服器遷移後就正常了。
  • 數據查詢的時候也是算作伺服器的入口流量的,如果訪問業務沒有這麼大量,而且沒有攻擊的問題的話可以往資料庫方面調查。
  • 有必要時常關註伺服器的 GC,可以及早發現問題。

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 虛擬機是如何調用方法的內容已經講解完畢,從本節開始,我們來探討虛擬機是如何執行方法中的位元組碼指令的。上文中提到過,許多Java虛擬機的執行引擎在執行Java代碼的時候都有解釋執行(通過解釋器執行)和編譯執行(通過即時編譯器產生本地代碼執行)兩種選擇,在本章中,我們先來探討一下在解釋執行時,虛擬機執行 ...
  • 最近朋友需要一個人證比對軟體需要實現以下功能: 1. 通過攝像頭實時採集人臉圖像 2. 通過身份證讀卡器採集身份證信息 和 身份證照片 3. 使用實時人臉照片 和 身份證照片做相似度比對 4. 比對後返回相似度,或者返回同一人,非同一人 5. 實時採集照片 和 身份證信息照片存檔,方便以後查閱 ## ...
  • **註**:本文是根據官方網站翻譯得來,其中做了部分修改用於理解文章字義。 # mojo介紹 Mojo被設計為Python的超集,因此許多語言功能和你可能在Python中知道的概念可以直接翻譯成Mojo。例如一個 Mojo中的“Hello World”程式看起來和Python一模一樣: ``` pr ...
  • ## **1、為什麼有消息系統** ##### 1、解耦合 ##### 2、非同步處理 例如電商平臺,秒殺活動。 一般流程會分為: 1. 風險控制 2. 庫存鎖定 3. 生成訂單 4. 簡訊通知 5. 更新數據 通過消息系統將秒殺活動業務拆分開,將不急需處理的業務放在後面慢慢處理; 流程改為: 1. ...
  • 閱讀本文前,需要儲備的知識點如下,點擊鏈接直接跳轉。 [java線程詳解](https://www.cnblogs.com/star95/p/17583193.html) [Java不能操作記憶體?Unsafe瞭解一下](https://www.cnblogs.com/star95/p/1761943 ...
  • 項目架構:Spring5+SpringMVC+Mybatis 項目伺服器:Tomcat 9.0.71 整合SSM啟動時,啟動失敗,Tomcat控制台報錯:Artifact “xxx - xxxx“:war exploded:部署工件時出錯。請參閱伺服器日誌瞭解詳細信息 查看Tomcat日誌:嚴重 [ ...
  • # if if if 判斷 和 if elif elif 判斷有什麼區別 ## 在Python中,if語句和if-elif-else語句都用於條件控制,但它們在處理條件和執行邏輯上有一些區別。 ### if語句:if語句用於執行一系列條件之一的代碼塊。 - 你可以使用多個if語句來檢查多個條件,但每 ...
  • 之前給大家推薦了很多後臺模版,有讀者希望推薦一些跟通用的好看組件,畢竟出了後臺還有很多其他場景嘛。所以,今天繼續給大家推薦一個廣受好評的UI組件庫:[**NextUI**](https://blog.didispace.com/tj-opensource-nextui/) ![NextUI](htt ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...