記一次OOM查詢處理過程
記一次OOM查詢處理過程
-
問題的爆出及分析排查現場
-
排查後的解決方案
-
項目的jvm參數
-
總結
一、問題的爆出及分析排查現場
服務偶爾會出現不可用的情況,導致出現time out,然後我迅速登錄現場,直接查看當時的gc日誌,不廢話,直接上圖
通過這個圖可以發現在10點7分、8分的時候頻繁Full GC,但是GC之後 年輕代,老年代並沒有少。並且Full GC時長5s8多,造成stop-the-world5s8,因此應用程序會出現無影響。再貼一張圖
通過這個可以看到內存慢慢的通過GC降下來了
根據這些信息基本可以斷定,是由於什麽操作導致內存飆升,並且都是一些大對象,就是由於這些大對象導致年輕代放不下,因此直接進入老年代(分配擔保策略),而這個操作占用的內存大,因此觸發了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查詢處理過程