1. 程式人生 > >記一次OOM查詢處理過程

記一次OOM查詢處理過程

poi pri eve ctime oop spa 進行 無需 頻繁

記一次OOM查詢處理過程

  • 問題的爆出及分析排查現場

  • 排查後的解決方案

  • 項目的jvm參數

  • 總結

一、問題的爆出及分析排查現場

  服務偶爾會出現不可用的情況,導致出現time out,然後我迅速登錄現場,直接查看當時的gc日誌,不廢話,直接上圖

技術分享圖片

通過這個圖可以發現在10點7分、8分的時候頻繁Full GC,但是GC之後 年輕代,老年代並沒有少。並且Full GC時長5s8多,造成stop-the-world5s8,因此應用程序會出現無影響。再貼一張圖

技術分享圖片

通過這個可以看到內存慢慢的通過GC降下來了

根據這些信息基本可以斷定,是由於什麽操作導致內存飆升,並且都是一些大對象,就是由於這些大對象導致年輕代放不下,因此直接進入老年代(分配擔保策略),而這個操作占用的內存大,因此觸發了Full GC(

jvm 觸發Full GC的情況 https://blog.csdn.net/chenleixing/article/details/46706039/ ),再加上又比較耗時,因此頻繁Full GC,因為對象一直被強引用著,導致無法被清除.

接下來立馬使用jdk 自帶的工具 jmap進行dump,然後使用jdk自帶的jvisualvm進行dump文件分析,分析圖如下:

技術分享圖片

通過這個圖可以看到Xobj相關的對象大小占到內存的61%左右,由於我對於這個項目又比較熟悉,所以很快就能定位是由於操作Excel文件導致的.因為一看這個就知道是和xml解析相關的

當然也可以通過分析工具來定位到比較具體的點的信息。

當定位是和Excel相關時,緊接著看了下和excel相關的處理文件,發現文件都不大,最大的也就40萬左右,7M而已,那麽為什麽會產生這麽大的內存對象內,於是乎,就去百度,然後得到的結果大概是poi讀取excel有兩種模式,一種user model 另一種event mode, 而我使用的是user model 模式,這種模式使用的內存和cpu 較之 event model都要大很多。而我為了方便,又是將excel 文件全部讀取然後進行處理。

二、排查後的解決方案

   解決方案分兩步走,一步是代碼層面、一步是jvm參數方面

    代碼層面:讀取excel文件使用event model 方式,並且每讀取100行,便處理然後釋放引用,這樣便於被GC回收

 jvm參數方面:之前年輕代是300M左右,而堆的總內存大小是4G,感覺年輕代的設置不太合理,因此調到800M,然後進行觀察,觀察後可進行適當的調整

三、項目的jvm參數

-XX:CICompilerCount=3 	設置編譯線程的數量。JVM在server模式下默認是2,在client模式下默認是1。如果使用分層編譯的話,這個值會擴展到與CPU核數一樣的值。
-XX:InitialHeapSize=4294967296 	設置堆的初始值
-XX:InitialTenuringThreshold=5  設置初始的對象在新生代中最大存活次數
-XX:MaxHeapSize=4294967296 	設置堆分配的最大值,單位字節
-XX:MaxNewSize=348913664 	新生代占整個堆內存的最大值。從Java1.4開始, MaxNewSize成為 NewRatio的一個函數
-XX:MinHeapDeltaBytes=196608 

-XX:OldPLABSize=16 
-XX:OldSize=3946053632 

 
-XX:+UseCompressedOops  可以壓縮指針,起到節約內存占用的新參數。使用compressed pointers。這個參數默認在64bit的環境下默認啟動,但是如果JVM的內存達到32G後,這個參數就會默認為不啟動,因為32G內存後,壓縮就沒有多大必要了,要管理那麽大的內存指針也需要很大的寬度了
-XX:SurvivorRatio=8  Eden與Survivor的占用比例。例如8表示,一個survivor區占用 1/8 的Eden內存,即1/10的新生代內存,為什麽不是1/9? 因為我們的新生代有2個survivor,即S1和S22。所以survivor總共是占用新生代內存的 2/10,Eden與新生代的占比則為 8/10。
-XX:MaxMetaspaceSize=512M  這個參數用於限制Metaspace增長的上限,防止因為某些情況導致Metaspace無限的使用本地內存,影響到其他程序。在本機上該參數的默認值為4294967295B(大約4096MB)
-XX:+UseCompressedClassPointers
-XX:CompressedClassSpaceSize=512M  的調優只有當-XX:+UseCompressedClassPointers開啟了才有效
-XX:MaxTenuringThreshold=5	設置對象在新生代中最大的存活次數,最大值15,並行回收機制默認為15,CMS默認為4
 
-------------------CMS相關參數-------------------
-XX:CMSInitiatingOccupancyFraction=70  使用cms作為垃圾回收使用70%後開始CMS收集

 
-------------------收集器設置-------------------
-XX:+UseConcMarkSweepGC   
	指 定在 Old Generation 使用 concurrent cmark sweep gc,gc thread 和 app thread 並行 ( 在 init-mark 和 remark 時 pause app thread). app pause 時間較短 , 適合交互性強的系統 , 如 web server 
-XX:+UseParNewGC 
	設置年輕代為並行收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設置,所以無需再設置此值。
-------------------堆設置-------------------
-Xms4096m  
	指定 jvm 的最小 heap 大小 , 如 :-Xms=4g , 高並發應用, 建議和-Xmx一樣, 防止因為內存收縮/突然增大帶來的性能影響。 
-Xmx4096m  
	指定 jvm 的最大 heap 大小
-XX:NewSize=348913664 
	設置年輕代大小
-XX:SurvivorRatio=8  
	指 定 New Generation 中 Eden Space 與一個 Survivor Space 的 heap size 比例 ,-XX:SurvivorRatio=8, 那麽在總共 New Generation 為 10m 的情況下 ,Eden Space 為 8m 

-------------------垃圾回收統計信息-------------------
-XX:+PrintGC 
	輸出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
			  [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDateStamps 
	GC發生的時間信息
-XX:+PrintGCDetails 
	輸出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
              [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps
	輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-Xloggc:logs/gc.log.201808142235  
	與上面幾個配合使用,把相關日誌信息記錄到文件以便分析

-------------------3個設置滾動記錄GC日誌的參數  測試一下    -------------------
-XX:+UseGCLogFileRotation 
	打開或關閉GC日誌滾動記錄功能,要求必須設置 -Xloggc參數
-XX:NumberOfGCLogFiles=1 
	設置滾動日誌文件的個數,必須大於1
	日誌文件命名策略是,<filename>.0, <filename>.1, ..., <filename>.n-1,其中n是該參數的值
-XX:GCLogFileSize=512M
	設置滾動日誌文件的大小,必須大於8k
	當前寫日誌文件大小超過該參數值時,日誌將寫入下一個文件
	

四、總結

  這個示例告訴我們,使用任何第三方jar,都需要進行嚴格的測試,確保不會對現有的系統造成傷害。開發人員需要對jvm進行了解,特別是GC這塊,因為你new 的每一行代碼都是和GC相關的,至少密不可分,知道的越多,了解的越清楚,才能保證寫出好的 code ,有些坑不一定要踩

記一次OOM查詢處理過程