[JVM] Java Heap Dump 分析 [轉載]

0 序

  • 緣起
  • 近期項目上我負責的微服務出現了難以排查的問題,目前懷疑是 JVM線程方面的情況,但也需從 heap dump方面進一步印證,故需深入瞭解heap dump文件內容的各項含義。
  • 本文主要轉載了網友的觀點,詳見:參考文獻

1 Heap Dump 分析

1.1 heapdump 簡介

  • heapdump文件是一個二進制文件,它保存了某一時刻JVM堆的對象使用情況
    • heapdump文件是指定時刻的Java堆棧的快照,是一種鏡像文件
  • Heap Dump中主要包含當生成快照堆中的java對象和類的信息,主要分爲如下幾類:
  • 對象信息:類名、屬性、基礎類型和引用類型
  • 類信息:類加載器、類名稱、超類、靜態屬性
  • gc roots:JVM中的一個定義,進行垃圾收集時,要遍歷可達對象的起點節點的集合
  • 線程棧和局部變量:快照生成時候的線程調用棧,和每個棧上的局部變量

1.2 heapdump 用途

  • heapdump是診斷與【JVM內存】相關的問題的重要手段,例如:內存泄漏、垃圾回收問題和java.lang.OutOfMemoryError。同時也是優化內存消耗的重要手段。

1.3 JVM內存結構(簡介)

說起heapdump,瞭解jvm 的內存結構,會更有助於對heapdump的使用。
JVM定義了若干個程序執行期間使用的數據區域。這個區域裏的一些數據在JVM啓動的時候創建,在JVM退出的時候銷燬。而其他的數據依賴於每一個線程,在線程創建時創建,在線程退出時銷燬。
jvm結構概覽如下:(各塊區域詳細解釋不在此說明,百度即可查到)

  • JVM內存模型中的這些區域,都是有大小限制的,當然也可以通過JVM提供的參數來設置這些區域所佔內存的大小。

運行時各區塊的描述如下

-Xms :初始堆大小(默認物理內存1/64);-Xmx :最大堆大小(默認物理內存1/4).。
-Xss:表示每個線程棧的大小。
-Xmn:表示新生代(年輕代)的大小
-XX:NewRatio:默認爲2,表示新生代佔年老代的1/2,佔整個堆內存的1/3。
-XX:SurvivorRatio:默認爲8,表示一個survivor區佔用1/8的Eden內存,即1/10的新生代內存。
-XX:MaxMetaspaceSize: 設置元空間最大值, 默認是-1, 只受限於本地內存大小。
-XX:MetaspaceSize: 指定元空間觸發Full Gc的初始閾值(元空間無固定初始大小), 以字節爲單位。

1.4 JVM內存爲何會溢出?

  • JVM根據generation(代)來進行GC,絕大多數的對象都在young generation被分配,也在young generation被收回,當young generation的空間被填滿,GC會進行minor collection(次回收),速度非常快。
    • 其中,young generation中未被回收的對象被轉移到tenured generation,當tenured generation被填滿時,即觸發major collection(FULL GC主回收),整個應用程序都會停止下來直到回收完成。
  • 因此,產生內存溢出錯誤原因一般出於以下原因:
  1. JVM內存過小,或配置不合理
  2. 程序內存泄露導致的對象無法回收
  3. 產生的對象超過了超過了堆的大小

1.5 如何生成、導出heapdump?

1.5.1 方式1:命令生成

jmap -dump:live,format=b,file=heapdump.hprof <pid>

# 如下命令亦可
jcmd <pid> GC.heap_dump heapdump.hprof

最近的實際項目中我是這麼用的:

jcmd {PID} VM.uptime
jcmd {PID} GC.heap_dump heap-dump-{PID}-$(date +'%Y%m%d%H%M%S').hprof
jcmd {PID} Thread.print > thread-dump-{PID}-$(date +'%Y%m%d%H%M%S').tdump
jcmd {PID} PerfCounter.print > perf-counter-print-{PID}-$(date +'%Y%m%d%H%M%S').txt

參見 : [JVM/APM] 應用診斷工具之jcmd - 博客園/千千寰宇

1.5.2 方式2:配置Java 啓動參數生成

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tmp/heapdump.hprof

2 heap dump文件分析

2.1 基於 MAT 工具的heap dump分析

2.1.1 MAT 工具簡介

  • mat(Eclipse Memory Analyzer tool),是一個快速且功能豐富的Java堆分析器,可幫助您查找內存泄漏並減少內存消耗。
  • 使用Memory Analyzer分析具有數億個對象的高效堆轉儲,快速計算對象的保留大小,查看誰阻止垃圾收集器收集對象,運行報告以自動提取泄漏嫌疑者。

2.1.2 術語解釋

在使用mat 前,先了解一些術語,便於工具的使用。

  • Shallow heap:一個對象本身佔用的堆內存大小,也就是對象頭加成員變量(不是成員變量的值)的總和。
    • 如:一個對象中,每個引用佔用8或64位,Integer佔用4字節,Long佔用8字節等等。
  • Retained Heap:如果一個對象被釋放掉,那會因爲該對象的釋放而減少引用進而被釋放的所有的對象(包括被遞歸釋放的)所佔用的heap大小。
    • 即對象被垃圾回收器回收後能被GC從內存中移除的所有對象之和。
    • 相對於shallow heap,Retained heap可以更精確的反映一個對象實際佔用的大小(若該對象釋放,retained heap都可以被釋放)。
  • gc root: 在java語言中,都是通過可達性分析來判定對象是否存活的。
    • 此算法的基本思路是:通過一系列的稱爲“GC Roots”的對象作爲起點,從這些節點向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連,則證明此對象是不可達的,因此能被GC 回收掉。
      • 因此,可以得出,只有引用類型的變量才被認爲是Roots,值類型的變量永遠不被認爲是Roots。
    • GC ROOT的目標對象是要以當前還在存活的對象集合。
      • 因此,必須要選取確定存活的引用類型對象,GC管理的區域是java的堆,虛擬機棧、方法區和本地方法棧不被GC所管理。
      • 因此,選用這些區域內引用的對象作爲GC Roots,是不會被GC回收的。

2.1.3 MAT 功能模塊

常用到的功能包括:

  • Histogram直方圖
  • Dominator Tree
  • Top Components
  • Top Consumers
  • Leak Suspects

如下進行逐一介紹。 打開dump後概覽圖如下:

爲方便後續功能理解使用,先闡述幾個通用功能

  1. List object:其下有with outgoing references,with incoming references兩個選項。

with outgoing references:查看當前對象持有的外部對象引用(在對象關係圖中爲從當前對象指向外的箭頭)
with incoming references:查看當前對象被哪些外部對象所引用(在對象關係圖中爲指向當前對象的箭頭)

  1. Paths to GC Roots:從當前對象到GC roots的路徑,這個路徑解釋了爲什麼當前對象還能存活,對分析內存泄露很有幫助,這個查詢只能針對單個對象使用。其下有很多選項,在查詢到GC root的路徑時,是包含所有引用,還是排除一些類型的引用(如軟引用、弱引用、虛引用),從GC角度說,一個對象無法被GC,一定是因爲有強引用存在,其它引用類型在GC需要的情況下都是可以被GC掉的,所以可以使用 exclude all phantom/weak/soft etc. references 只查看GC路徑上的強引用

2.1.3.1 Histogram / 直方圖

Histogram:直方圖,可以列出內存中的對象,對象的個數以及大小。

該視圖以Class類的維度展示每個Class類的實例存在的個數、 佔用的 [Shallow內存] 和 [Retained內存] 大小,可以分別排序顯示。
從Histogram視圖可以看出,哪個Class類的對象實例數量比較多,以及佔用的內存比較大,Shallow Heap與Retained Heap的區別會在後面的概念介紹中說明。

不過,多數情況下,在Histogram視圖看到實例對象數量比較多的類都是一些基礎類型,如char[]、String、byte[],所以僅從這些是無法判斷出具體導致內存泄露的類或者方法的,可以使用 List objectsMerge Shortest Paths to GC roots 等功能繼續鑽取數據。
如果Histogram視圖展示的數量多的實例對象不是基礎類型,是有嫌疑的某個類,如項目代碼中的bean類型,那麼就要重點關注了。

2.1.3.2 Dominator Tree / 支配樹

Dominator Tree:支配樹,可以列出那個線程,以及線程下面的那些對象佔用的空間。

該視圖以實例對象的維度展示當前堆內存中Retained Heap佔用最大的對象,以及依賴這些對象存活的對象的樹狀結構
視圖中展示了實例對象名、Shallow Heap大小、Retained Heap大小、以及當前對象的Retained Heap在整個堆中的佔比
Dominator Tree支配樹可以很方便的找出佔用Retained Heap內存最多的幾個對象,並表示出某些objects的是因爲哪些objects的原因而存活,在之後的 Dominator Tree概念 部分會對支配樹做更詳細的說明和舉例

2.1.3.3 Top consumers

Top consumers:通過圖形列出最大的object

可以通過按包名查看區分佔用,根據包我們知道哪些公共用的到jar或自己的包占用

2.1.3.4 Thread Overview / 線程概覽

在Thread Overview視圖可以看到:線程對象/線程棧信息、線程名、Shallow Heap、Retained Heap、類加載器、是否Daemon線程等信息
在分析內存Dump的MAT中還可以看到線程棧信息,這本身就是一個強大的功能,類似於jstack命令的效果
而且還能結合內存Dump分析,看到線程棧幀中的本地變量,在左下方的對象屬性區域還能看到本地變量的屬性,真的很方便

2.1.3.5 Leak Suspects / 泄露猜想

Leak Suspects通過MA自動分析泄漏的原因

  • Leak Suspects 是MAT幫我們分析的可能有內存泄露嫌疑的地方,可以體現出哪些對象被保持在內存中,以及爲什麼它們沒有被垃圾回收。MAT工具分析了heap dump後在界面上非常直觀的展示了一個餅圖,該圖深色區域被懷疑有內存泄漏,
  • 接下來是一個簡短的描述,告訴我們哪些線程佔用了大量內存,並且明確指出system class loader加載的實例有內存聚集,並建議用關鍵字對應進行檢查。在下面還有一個“Details”鏈接,可以查看明細信息。

  • (1)Details的最開始是Description描述,和前一個頁面對內存泄露嫌疑點的描述一致,下面有一些與懷疑的內存泄露點關聯的查詢結果展示,是分析報告中認爲可能會存在問題,協助我們深入分析問題根源的。
  • (2)Shortest Paths To the Accumulation Point:當前對象的 Path to GC roots,即到GC roots的路徑。作用是可以分析是由於和哪個GC root相連導致當前Retained Heap佔用相當大的對象無法被回收。
  • (3)Accumulated Objects in Dominator Tree:以對象的維度展示了以當前對象爲根的 Dominator Tree支配樹,可以方便的看出受當前對象“支配”的對象中哪個佔用Retained Heap比較大。
  • (4)Accumulated Objects by Class in Dominator Tree:展示了以當前對象爲根的Dominator Tree支配樹,並以Class類分組。
  • (5)Thread Detail:Detail明細的最後由於當前懷疑泄露點爲main Thread線程對象,故展示了線程明細信息,調用棧信息,對分析內存溢出的發生位置很有幫忙

X 參考文獻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章