1. 程式人生 > >虛擬機器堆轉儲快照生成以及分析

虛擬機器堆轉儲快照生成以及分析

  通過程式生成的dump檔案來分析故障原因所在。本文給大家展示堆轉儲快照生成以及分析過程。   第一種:使用暴力手段來生成dump檔案— -XX:+HeapDumpOnOutOfMemoryError引數   測試的類如下:
import java.util.*;
public class Test{
   public static void main(String[] args) throws Exception{
 List<TestObject> list=new ArrayList<TestObject>();
 while(true){
    list.add(new TestObject());
 }
   }
   static class TestObject{}
}

  測試之前分析:這是個死迴圈,程式記憶體肯定不夠用,因為他一直沒有釋放,所以肯定會報錯。   執行此程式:
E:\javatest\test>java -Xms20M -Xmx20M -Xmn10M  -XX:+HeapDumpOnOutOfMemoryError Test
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9900.hprof ...
Heap dump file created [29679220 bytes in 0.413 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Unknown Source)
        at java.util.Arrays.copyOf(Unknown Source)
        at java.util.ArrayList.grow(Unknown Source)
        at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
        at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
        at java.util.ArrayList.add(Unknown Source)
        at Test.main(Test.java:6)
  其中設定此虛擬機器的堆初始值xms和最大值xmx相同,防止他自動擴建,xmn引數是指分配新生代大小。   自動生成的dump檔案java_pid9900.hprof.開啟此hprof檔案:   若有時dump檔案很大,需要設定jhat引數:jhat -J-Xmx2048m <heap dump file>,但是預設情況下是1024m。
E:\javatest\test>jhat java_pid9900.hprof
Reading from java_pid9900.hprof...
Dump file created Sun Aug 03 15:58:32 CST 2014
Snapshot read, resolving...
Resolving 1262585 objects...
Chasing references, expect 252 dots............
...............................................
...............................................
Eliminating duplicate references...............
...............................................
............................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

  預設埠號是7000,可以-port 指定埠號,開啟7000埠:      其中jhat的dump檔案的結構如上。對於最後物件查詢語言執行,請參考部落格http://blog.csdn.net/gtuu0123/article/details/6039592      上文是通過jhat命令開啟訪問dump檔案,另外一種形式,是通過eclipse或myeclipse外掛MAT來訪問dump檔案。   外掛地址:http://www.eclipse.org/mat/downloads.php   其中下載下來,至於安裝,大家根據外掛安裝方式,eclipse或myeclipse其實是一樣,其中我都喜歡手動安裝外掛,使用link方式。   本文測試是myeclipse10.7.1,mat外掛1.2.1.   上文是通過dos執行的,並且java執行時配置執行引數,根據上文的配置引數,在IDE中同樣配置:     

    開啟hprof檔案,File—Open:       選擇開啟的report:        開啟後,重新整理refresh專案,專案下會生成一系列檔案:      預設開啟的Leak report:        Leak Report:記憶體洩露報告。既然有洩露圖,則說明程式中存在記憶體洩露。先解釋一下記憶體洩露和記憶體溢位區別。   記憶體溢位:可以這樣理解,物件處於存活狀態,然而記憶體分配不夠,對於這種狀況,需要檢查程式碼物件生命週期。   記憶體洩露:針對溢位而言,GC無法回收物件,物件佔用記憶體無法釋放。既然提到GC無法回收物件,那就應該理解GC是如何回收物件的,在此簡單通俗而言,GC會從root 引用鏈向下檢查回收,被root引用鏈指向的物件都可以自動回收。但是若記憶體中存在這樣兩個物件,互相引用,而兩個物件都沒有被root引用(直接或間接),那麼GC是無法檢測到這種引用狀況,因此會產生記憶體洩露。所以,對於記憶體洩露來說,記憶體中物件狀態不一定是死亡狀態。      根據Leak Report圖,發現總共Total堆大小14.3(我設定了xms和xmx都是20m,不知為啥total是14.3而非20m,並且通過Problem suspect1中main佔用大小,可以得知,這個圖大小是個股估值而非精確值)   總共堆大小14.3m,其中a佔用13.9m,剩下363b,那我們繼續看a的佔用情況。   a區域Problem Suspect1當前執行緒main方法佔用97.51%,繼續檢視detail資訊:


  Shortest Path to the accumulated point:最短距離到聚集點   main執行緒的Shallow Heap104b,而Retained Heap14m。   堆積的物件圖中:class main下有個Object[]類,其中Shallow Heap4m,而Retained Heap14m。並且Object[]引用的類就是咱們程式中定義的TestObject空靜態類了。其中TestObject類的Shallow Heap8b,Retained Heap也是8b。   並且Accumulated Object by Class中,TestObject物件達到1,215,487個,佔用堆大小9,723,896位元組。      對於以上資訊中經常提到Shallow Heap大小和Retained Heap大小,在此解釋一下其含義。      Shallow Heap:物件自身佔用的大小。不包括引用的物件。   Retained Heap:是指當物件本身被GC後,Heap上共釋放的大小。其中Retained Heap=自身佔用大小(Shallow Heap)+當前物件直接引用或間接引用的物件的大小。   Shallow Heap和Retained Heap官網英文地址:http://help.eclipse.org/luna/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html
  既然瞭解到Shallow Heap和Retained Heap含義,那上文的TestObject為啥Shallow Heap=8B。那就要了解記憶體堆中一個物件如何存放的。   一個物件格式=A+B+C+D;   A:物件頭:佔用很少資訊,描述Object當前狀態,在Hot Spot虛擬機器中,一個普通物件,佔用8 bytes;陣列,佔用 12 bytes,包含普通物件的 8 bytes + 4 bytes(陣列長度)   B:基本型別:boolean、byte 佔用 1 byte,char、short 佔用 2 bytes,int、float 佔用 4 bytes,long、double 佔用 8 bytes   C: 引用型別:4 bytes   D:填充物,在Hotspot中,每個物件佔用的總空間是以8的倍數計算的,物件佔用總空間(物件頭+宣告變數)不足8的倍數時候,自動補齊。而,這些被填充的空間,我們可以稱它為“填充物”。   TestObject中就是一個空類,所以只有8bytes。   若TestObject類中有一個String name屬性,則佔用大小=物件頭8bytes+引用型別4bytes   若TestObject類中有一個boolean isRead屬性,則佔用大小=物件頭8bytes+boolean基本型別1byes(因為是9bytes,不是8的倍數,所以需要補齊,因此填充物=7bytes)+7bytes填充物   若TestObject類中有一個String name屬性,一個int age屬性,則佔用大小=物件頭8bytes+name屬性4bytes+age屬性4bytes=16bytes
 除了上文中的Leak Report,mat中還有很多檢視,開啟i-Overview概要     這是通過引數生成的dump檔案,那第二種形式則通過優雅方式jmap方式。   第二種方式:jmap生成dump檔案:   jmap檢視堆情況:
e:\commonsoftware\jdk1.7\bin>jps
7892 Jps
10588 Test
9372

e:\commonsoftware\jdk1.7\bin>jmap -heap 10588
Attaching to process ID 10588, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 24.65-b04

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 20971520 (20.0MB)
   NewSize          = 10485760 (10.0MB)
   MaxNewSize       = 10485760 (10.0MB)
   OldSize          = 4194304 (4.0MB)
   NewRatio         = 2
   SurvivorRatio    = 8
   PermSize         = 12582912 (12.0MB)
   MaxPermSize      = 67108864 (64.0MB)
   G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 9437184 (9.0MB)
   used     = 745360 (0.7108306884765625MB)
   free     = 8691824 (8.289169311523438MB)
   7.898118760850695% used
Eden Space:
   capacity = 8388608 (8.0MB)
   used     = 745360 (0.7108306884765625MB)
   free     = 7643248 (7.2891693115234375MB)
   8.885383605957031% used
From Space:
   capacity = 1048576 (1.0MB)
   used     = 0 (0.0MB)
   free     = 1048576 (1.0MB)
   0.0% used
To Space:
   capacity = 1048576 (1.0MB)
   used     = 0 (0.0MB)
   free     = 1048576 (1.0MB)
   0.0% used
tenured generation:
   capacity = 10485760 (10.0MB)
   used     = 0 (0.0MB)
   free     = 10485760 (10.0MB)
   0.0% used
Perm Generation:
   capacity = 12582912 (12.0MB)
   used     = 153872 (0.1467437744140625MB)
   free     = 12429040 (11.853256225585938MB)
   1.2228647867838542% used

11722 interned Strings occupying 908888 bytes.

  jmap生成bin檔案,格式如下:
E:\commonsoftware\jdk1.7\bin>jps
9372
6248 Test
12268 Jps

E:\commonsoftware\jdk1.7\bin>jmap -F -dump:format=b,file=testtestdump.bin 6248
Attaching to process ID 6248, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 24.65-b04
Dumping heap to testtestdump.bin ...
Heap dump file created

  解釋:在dos下執行,執行很快,jps還沒捕捉到pid呢,java已經執行完畢,如何辦呢,java方法如何監控呢,無論使用純文字jmap還是視覺化的jconsole,都需要一個pid,其實在方法中先讓程式sleep一會,然後捕捉到pid,再進行監控。
import java.util.*;
public class Test{
   public static void main(String[] args) throws Exception{
      Thread.sleep(20000);
       List<TestObject> list=new ArrayList<TestObject>();
       while(true){
          list.add(new TestObject());
       }
   }
   static class TestObject{}
}

  工欲善其事必先利其器,方可事半功倍。



參考文獻:http://www.cnblogs.com/AloneSword/p/3821569.html           http://blog.csdn.net/jiangtongcn/article/details/8222685