通過這一個多月的努力,將FullGC從40次/天優化到近10天才觸發一次,而且YoungGC的時間也減少了一半以上,這么大的優化,有必要記錄一下中間的調優程序,
對于JVM垃圾回收,之前一直都是處于理論階段,就知道新生代,老年代的晉升關系,這些知識僅夠應付面試使用的,前一段時間,線上服務器的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記憶體大小=年輕代大小 + 年老代大小 + 持久代大小,持久代一般固定大小為64m,所以增大年輕代后,將會減小年老代大小,此值對系統性能影響較大,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,發現前三次GullGC時,老年代占據的記憶體還不足30%,卻發生了FullGC,于是進行各種資料的調查,在blog.csdn.net/zjwstz/arti… 博客中非常清晰明了的說明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,可以及早發現問題,
看完三件事??
如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
-
點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力,
-
關注公眾號 『 java爛豬皮 』,不定期分享原創知識,
-
同時可以期待后續文章ing??
出處:club.perfma.com/article/185…
作者:coffeeboy
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/182747.html
標籤:其他
