今天整理的這篇文章,整理自以前記錄的日常筆記。
剛開始接觸JVM時,對待GC的日誌,是保持"逃避"態度的,線上部署的程式沒有達到期望的執行效率,或是預期目標時,往往會把問題的矛頭指向記憶體上。
是不是記憶體不夠?
GC日誌怎麼這麼多?佔用磁碟高達幾個G?
不想看到列印怎麼多,甚至暴力的將程式日誌遮蔽掉。
我也曾經這樣幹過,寫過的程式,同樣導致過記憶體溢位,甚至是主機宕機。
是的,java中的垃圾回收的確幫我們省了很多事,我們不像C/C++程式設計師一樣,
需要考慮分配(malloc)記憶體分配與(free)釋放記憶體,但我相信每個java開發的小夥伴都會遇到GC問題,無論是在程式的效能最佳化,還是故障分析上。
我們應該在一次又一次的失誤中獲得經驗和教訓,而不是去逃避或是草草解決問題了事。同樣的知識點,回顧兩次,就會產生 1 + 1 > 2 的效應 。
所以這篇文章我們來談談大家耳熟能詳的JVM的 GC日誌。
GC 日誌是什麼
首先來說一說GC的概念, GC 就是垃圾回收 (Garbage Collection) 的縮寫。
何為垃圾?
沒有任何引用指向的物件,被JVM視為垃圾。
當然,對應的判定演算法有:引用計數演算法、可達性分析演算法。對應的回收演算法有:標記清除、複製、標記整理、分代收集演算法。
那GC的日誌是什麼呢?
GC日誌是java虛擬機器產生的一種描述性的文字日誌。就像我們開發java程式需要輸出log日誌一樣。JAVA 虛擬機器用GC日誌來描述,垃圾回收的情況。
minor GC 和 Major GC
Minor GC,表示新生代GC,指發生在新生代的垃圾收集動作,所有的Minor GC都會觸發全世界的暫停(stop-the-world),停止應用程式的執行緒,不過這個過程非常短。
Major GC:老年代GC,指發生在老年代的 GC,也稱之為 Full GC。
GC 日誌有什麼用
透過GC日誌,我們能直觀的看到記憶體清理的基本工作過程。
瞭解垃圾回收的一些行為,何時在Young(年輕代)何時在回收Old(老年代),並且展示垃圾回收使用到多少資源。
儘管現在Java程式的視覺化監控工具已經很多了,但是 GC 日誌對於開發人員來說,是我們快速定位潛在的記憶體故障和效能瓶頸最直觀的資訊之一。
ES 的 GC分配失敗(GC Allocation Failure)
遇到線上GC問題時的日誌也是很寶貴的分析素材,這裡的GC Allocation Failure圖片引用自一位群友的ES節點GC日誌。
GC Allocation Failure 是我們經常遇到的一種GC日誌。
分配失敗代表著在JVM的Eden區中沒有更多的空間來分配物件了,這是minor GC的正常日誌。
那別光說不練,那我們來看看GC Allocation Failure的日誌內容是什麼樣的。
是不是感覺很亂?
沒關係,我們依次來分析分析。
我們按時間分割,可以知道,截圖中一共有兩行日誌,先來看日誌一:
2020-03-17T19:03:19.701+0800: 6664.686:Total time for which application threads were stopped: 0.0313360 seconds, Stopping threads took: 0.0000925 seconds
毋庸置疑,首先映入眼簾的是帶時區的日誌時間。
其次 Total time for which application threads were stopped 表示 所有應用執行緒暫停了 0.0313360 秒。
其中等待所有應用執行緒到達 【安全點】 用了0.0000925 秒。
暫停的這段時間,其實就是花在了GC上面。後續第二行的 real=0.03 secs 和這裡相對應。
剛才提到了 安全點 ,那簡單提一提,我們知道在 Java 的執行緒有幾個不同的狀態。也知道執行緒如果被 “打斷” 會出現什麼樣的問題。所以對於設計者來說,需要讓執行緒 “跑” 到安全點上,再停頓。當處於安全點時,執行緒的狀態是確定的,這樣JVM就可以安全的進行一些操作,比如執行垃圾回收。
安全點的位置包括:
如果有執行緒一直沒有進入到安全點,就會導致GC時JVM停頓時間延長。
再來看日誌二:
1) 2020-03-17T19:03:20.118+0800: 6665.102:2)[GC (Allocation Failure) 2020-03-17T19:03:20.118+0800: 6665.102:3)[ParNew Desired survivor size 8716288 bytes, new threshold 6 (max 6)4)- age 1: 6826872 bytes, 6826872 total5)- age 2: 1060216 bytes, 7887088 total6): 149828K->8895K(153344K), 0.0361997 secs]7)6272826K->6139400K(8371584K),0.0363166 secs]8) [Times: user=0.07 sys=0.00, real=0.03 secs]
第一行,為日誌輸出的時間。
第二行,表明了進行了一次 GC 回收,注意,由於這裡沒有 Full 關鍵字 ,表明是一次 Minor GC,並指明瞭 GC 的時間。Allocation Failure則表示GC的原因是在年輕代中沒有了足夠的空間來儲存資料了。
第三行,ParNew 同樣指明瞭本次 GC 是發生在年輕代,並且使用的是ParNew垃圾收集器。該收集器採用複製演算法回收記憶體,期間會停止其他工作執行緒,即Stop The World。
第三、四、五行,表示每次年輕代 GC 之後列印 survivor 區域內物件的年齡分佈, threshold則表示設定的晉升老年代的年齡閾值為6。
第六行,分別表示GC前年輕代的使用容量,GC 後該區域使用容量,括號內是該區域的總容量。最後是該記憶體區域GC耗時,單位是秒。
第七行,分別表示堆記憶體在垃圾回收之間的大小、堆記憶體在垃圾回收之後的大小,堆區的總大小。
可以看到在 GC 後,回收物件佔比很少。
第八行,顯示三個耗時,分別是使用者態耗時、核心態耗時、總耗時。
從以上資訊我們可以分析得出以下結論:
本次 GC 新生代減少了:149828 - 8895 = 140933K。
堆記憶體區域共減少了:6272826 - 6139400 = 133426K。
圖來自網路
再把兩個等號後的結果相減:140933 - 133426 = 7507K
說明該次共有7507K(7.3M)記憶體從年輕代移到了老年代,可以看出來數量並不多,說明都是生命週期短的物件,只是這種物件有很多。
我們需要的是儘量避免Full GC的發生,讓物件儘可能的在年輕代就回收掉,所以這裡可以稍微增加一點年輕代的大小,讓那 7.3M 的資料也儲存在年輕代中。
ES 的 Old GC
上面介紹了 年輕代的GC日誌,下面我們來說一說 老年代的 GC 日誌 ,其實和年輕代分析的方法一樣。
還是先列出日誌:
[gc][238384] overhead, spent [2.2s] collecting in the last [2.3s][2020-03-18T01:01:29,020][INFO ][o.e.m.j.JvmGcMonitorService][eS] [gc][old][238385][160772] duration [5s],collections [1]/[5.1s], total [5s]/[4.4d], memory [945.4mb]->[958.5mb]/[1007.3mb],all_pools {[young] [87.8mb]->[100.9mb]/[133.1mb]}{[survivor] [0b]->[0b]/[16.6mb]}{[old] [857.6mb]->[857.6mb]/[857.6mb]}
直接來解釋一下吧。第一行指明瞭這是第 238384次 GC 在最近 2.3 s 內花了 2.2s 用來做垃圾收集。
相信解讀過年輕代的GC,理解第二行的含義並不複雜了。
[gc][本次是 old GC][這是第228385次 GC 檢查][從 JVM 啟動至今發生的第 160772次 GC] duration [本次檢查到的 GC 總耗時 5 秒,可能是多次的加和],
collections [從上次檢查至今總共發生1次 GC]/[從上次檢查至今已過去 5.1 秒],
total [本次檢查到的 GC 總耗時為 5 秒]/[從 JVM 啟動至今發生的 GC 總耗時為 4.4 天],
memory [ GC 前 Heap memory 空間]->[GC 後 Heap memory 空間]/[Heap memory 總空間],
all_pools(分代部分的詳情)
{[young 區][GC 前 Memory ]->[GC後 Memory]/[young區 Memory 總大小] }
{[survivor 區][GC 前 Memory ]->[GC後 Memory]/[survivor區 Memory 總大小] }{[old 區][GC 前 Memory ]->[GC後 Memory]/[old區 Memory 總大小] }
ES 的 GC 配置
-XX:+PrintGCDetails 表示列印GC的詳細日誌
-XX:+PrintGCDateStamps 表示需要展示列印GC的日期時間
-XX:+PrintGCApplicationStoppedTime 列印垃圾回收期間程式暫停的時間
日誌滾動,輸出到指定的日誌檔案中等等配置。
GC日誌圖示
如果前面的介紹還沒有太明白,這裡附上兩張GC日誌的圖片,以後遇到需要閱讀GC日誌的時候,可以用作工具查閱。
年輕代:
老年代:
總結(我的觀點)
GC 涉及到的知識點很多,本文可能只是冰山一角,從GC 日誌出發分析,讓讀者瞭解,GC日誌中每個數字的變化,意味著什麼。更多的,先前有介紹過線上 OOM 的一些排查思路和MAT 效能分析工具在實際案例中的使用,同時希望各位多多結合實際的案例來分析,做好知識儲備,即使線上出了 GC 的一些問題也能做到成竹在胸,調整出最優的JVM配置,提高線上程式的執行效率,儘量避免效能上的一些故障。最後在內容上做個小結
本文介紹了整理這篇筆記的緣由
介紹了GC日誌的基本概念
minor GC 和 Major GC的基本概念
說明了GC 日誌的作用
ES 的 GC分配失敗的日誌解讀
ES 的 Old GC的日誌解讀。
JAVA GC的圖示