Heap堆分析(堆轉儲、堆分析)


一、堆直方圖

  減少內存使用時一個重要目標,在堆分析上最簡單的方法是利用堆直方圖。通過堆直方圖我們可以快速看到應用內的對象數目,同時不需要進行完整的堆轉儲(因為堆轉儲需要一段時間來分析,而且會消耗大量磁盤空間)。

直方圖擅長識別由分配了一兩個特定類的過多實例所引發的問題。例如應用中的內存壓力是由一些特定的對象類型引起的,利用堆直方圖可以很快就能看出端倪。

1.1、通過jcmd獲得

堆直方圖可以通過jcmd命令獲得:

[ciadmin@2-103test_app ~]$ jcmd 26964 GC.class_histogram | more
26964:

 num     #instances         #bytes  class name
----------------------------------------------
   1:         91488       21270064  [C
   2:          9058       18963152  [B
   3:         80620        2579840  java.util.concurrent.ConcurrentHashMap$Node
   4:         24081        2119128  java.lang.reflect.Method
   5:         86860        2084640  java.lang.String
   6:         13013        1444264  java.lang.Class
   7:         24376        1170048  org.aspectj.weaver.reflect.ShadowMatchImpl
   8:         26822        1072880  java.util.LinkedHashMap$Entry
   9:           553         921168  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  10:         15903         890568  java.util.LinkedHashMap
  11:         12092         847832  [Ljava.util.HashMap$Node;
  12:           307         829712  [J
  13:         24376         780032  org.aspectj.weaver.patterns.ExposedState
  14:         12621         718696  [Ljava.lang.Object;
  15:          5433         686400  [I
  16:         36341         581456  java.lang.Object
  17:         17746         567872  java.util.HashMap$Node
  18:          1267         476392  java.lang.Thread
  19:         13207         422624  java.lang.ThreadLocal$ThreadLocalMap$Entry
  20:          2516         270336  [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;

 

說明:

1、字段說明

  • [C:字符數組
  • [B:字節數組
  • [Ljava.lang.Object:Object數組

2、GC.class_histogram輸出的僅包含活躍對象

1.2、通過jmap獲得

命令為:jmap -histo process_id

jmap的輸出中包含會被回收的對象(死對象)。要在看到直方圖之前強制執行一次Full GC,可以轉而運行下面的命令:

如下,在命令行中增加:live參數后,輸出的直方圖是Full GC之后的數據

[ciadmin@2-103test_app ~]$ jmap -histo:live 26964 | more

 num     #instances         #bytes  class name
----------------------------------------------
   1:         91488       21270064  [C
   2:          9058       18963152  [B
   3:         80620        2579840  java.util.concurrent.ConcurrentHashMap$Node
   4:         24081        2119128  java.lang.reflect.Method
   5:         86860        2084640  java.lang.String
   6:         13013        1444264  java.lang.Class
   7:         24376        1170048  org.aspectj.weaver.reflect.ShadowMatchImpl
   8:         26822        1072880  java.util.LinkedHashMap$Entry
   9:           553         921168  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  10:         15903         890568  java.util.LinkedHashMap
  11:         12092         847832  [Ljava.util.HashMap$Node;
  12:           307         829712  [J
  13:         24376         780032  org.aspectj.weaver.patterns.ExposedState
  14:         12621         718696  [Ljava.lang.Object;
  15:          5433         686400  [I
  16:         36341         581456  java.lang.Object
  17:         17749         567968  java.util.HashMap$Node
  18:          1267         476392  java.lang.Thread
  19:         13207         422624  java.lang.ThreadLocal$ThreadLocalMap$Entry
  20:          2516         270336  [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;
  21:          8056         257792  java.util.LinkedList
  22:         11664         247432  [Ljava.lang.Class;
  23:          4397         187064  [Ljava.lang.String;
  24:          1945         186720  org.springframework.beans.GenericTypeAwarePropertyDescriptor
  25:          5631         180192  java.lang.ref.WeakReference
  26:          3630         174240  java.util.HashMap

 

直方圖非常小,但獲取直方圖也需要幾秒時間。性能測試時需要注意它。

二、堆轉儲

直方圖擅長識別由分配了一兩個特定類的過多實例所引發的問題,但是要深度分析,就需要堆轉儲了。

2.1、使用jcmd進行堆轉儲

[ciadmin@2-103test_app pos-gateway-cloud]$ jcmd 26964 GC.heap_dump /home/ciadmin/pos-gateway-cloud/heap_dump.hprof
26964:
Heap dump file created

2.2、使用jmap進行堆轉儲

[ciadmin@2-103test_app pos-gateway-cloud]$ jmap -dump:live,file=/home/ciadmin/pos-gateway-cloud/heap_dump2.hprof 26964
Dumping heap to /home/ciadmin/pos-gateway-cloud/heap_dump2.hprof ...
Heap dump file created

jmap中包含live選項,會在堆轉儲前執行一次Full GC;jcmd默認就會這么做。如果因為某些原因,不希望包含其他對象(即死對象),可以在jcmd命令的最后加上-all。

2.3、自動堆轉儲

OutOfMemoryError是不可預料的,我們很難確定應該何時獲得堆轉儲。有幾個JVM標志可以起到幫助。

-XX:+HeapDumpOnOutOfMemoryError該標志默認為false,打開該標志,JVM會在拋出OutOfMemoryError時創建堆轉儲。

-XX:HeapDumpPath=<path>該標志知道了堆轉儲將被寫入的位置,默認為當前工作目錄下生產java_pid<pid>.hprof文件。

-XX:+HeapDumpAfterFullGC 這會在運行一次Full GC后生成一個堆轉儲文件。

-XX:+HeapDumpBeforeFullGC 這會在運行一次Full GC之前生成一個堆轉儲文件。

有的情況下,(入幫,因為執行了多次Full GC)會生成多個堆轉儲文件,這時JVM會在堆轉儲文件的名字上附加一個序號。

這兩條命令都會在指定目錄下創建一個命名為*.hprof的文件。生成之后,有很多工具可以打開該文件。以下是三個最常見的工具。

三、堆轉儲文件分析工具

jhat

  這是最原始的分析工具,它會讀取堆轉儲文件,並運行一個小型的HTTP服務器,該服務器運行你通過一系列網易鏈接查看堆轉儲信息。

[ciadmin@2-103test_app pos-gateway-cloud]$ jhat heap_dump.hprof 
Reading from heap_dump.hprof...
Dump file created Mon Mar 05 18:33:10 CST 2018
Snapshot read, resolving...
Resolving 751016 objects...
Chasing references, expect 150 dots......................................................................................................................................................
Eliminating duplicate references......................................................................................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

找一台帶瀏覽器的機器訪問它,http://ip:7000

更多信息見《九、jdk工具之jhat命令(Java Heap Analyse Tool)、jhat之一:對dump的結果在瀏覽器上展示

 

jvisualvm

jvisualvm的監視(Monitor) 選項卡可以從一個運行中的程序獲得堆轉儲文件,也可以打開之前生成堆轉儲文件。

更多信息見《八、jdk工具之JvisualVM、JvisualVM之一--(visualVM介紹及性能分析示例)

 

mat

開源的EclipseLink內存分析器工具(EclipseLink Memory Analyzer Tool,mat)可以加載一個或多個堆轉儲文件並執行分析。它可以生成報告,向我們建議可能存在問題的地方,也可以用於流量堆,並對堆執行類SQL的查詢。

特別指出的是:mat內置一功能:如果打開了兩個堆轉儲文件,mat有一個選項用來計算兩個堆中的直方圖之間的差別。

更多信息見《mat之一--eclipse安裝Memory Analyzer

 

對堆的第一遍分析通常涉及保留內存。一個對象的保留內存,是指回收該對象可以釋放出的內存量。

關於保留內存相關知識見《GC之二--GC是如何回收時的判斷依據、shallow(淺) size、retained(保留) size、Deep(深)size

 

四、內存溢出錯誤

在下面情況下,jvm會拋出內存溢出錯誤(OutOfMemeoryError):

  • JVM沒有原生內存可用;
  • 永久代(在java7和更早的版本中)或元空間(java8)內存不足;
  • java堆本身內存不足--對於給定的堆空間而言,應用中活躍對象太多;
  • JVM執行GC耗時太多;

1、原生內存不足

其原因與堆根本無關。在32位的JVM中,一個進程的最大內存是4GB,如果指定一個非常大的堆大小,比如說3.8GB,使應用的大小很接近4GB的限制,這很危險。

2、永久代或元空間內存不足

首先與堆無關,其根源可能有兩種情況:

  • 第一種情況是應用使用的類太多,超出了永久代的默認容納范圍;解決方案:增加永久代的大小
  • 第二種情況相對來說有點棘手:它涉及類加載器的內存泄漏。這種情況經常出現於Java EE應用服務器中。類加載導致內存溢出可以通過堆轉儲分析,在直方圖中,找到ClassLoader類的所有實例,然后跟蹤他們的GC根,看哪些對象還保留了對它們的引用。

示例:

3、堆內存不足

當確實是堆內存本身不足時,錯誤信息會這樣:

OutOfMemoryError:Java heap space

可能的原因有:

1、活躍對象數目在為其配置的堆空間中已經裝不下了。

2、也可能是應用存在內存泄漏:它持續分配新對象,卻沒有讓其他對象退出作用域。

不管哪種情況,要找出哪些對象消耗的內存最多,堆轉儲分析都是必要的;

4、達到GC的開銷限制

JVM拋出OutOfMemoryError的最后一種情況是JVM任務在執行GC上花費了太多時間:

OutOfMemoryError:GC overhead limit exceeded

當滿足下列所有條件時就會拋出該錯誤:

1、花在Full GC的時間超出了-XX:GCTimeLimit=N標志指定的值。默認為98

2、一次Full GC回收內存量少於-XX:GCHeapFreeLimit=N標志指定的值。默認值為2(2%)

3、上面兩個條件連續5次Full GC都成立(這個值無法調整)

4、-XX:+UseGCOverhead-Limit標志為true(默認也為true)

這四個條件必須都滿足。一般來說,連續5次Full GC以上,不一定會拋異常。即使98%時間在Full GC上,但每次GC期間釋放的堆空間會超過2%,這種情況下可以增加-XX:GCHeapFreeLimit的值。

還請注意,如果前兩個條件連續4次Full GC周期都成立,作為釋放內存的最后一搏,JVM中所有的軟引用都會在第五次Full GC之前被釋放。這往往會防止該錯誤,因為第五次Full GC很可能會釋放超過2%的堆內存(假設該應用使用了軟引用)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM