乾貨分享丨jvm系列:dump文件深度分析

摘要:java內存dump是jvm運行時內存的一份快照,利用它能夠分析是否存在內存浪費,能夠檢查內存管理是否合理,當發生OOM的時候,能夠找出問題的緣由。那麼dump文件的內容是什麼樣的呢?

JVM dump

java內存dump是jvm運行時內存的一份快照,利用它能夠分析是否存在內存浪費,能夠檢查內存管理是否合理,當發生OOM的時候,能夠找出問題的緣由。那麼dump文件的內容是什麼樣的呢?咱們一步一步來java

獲取JVM dump文件

獲取dump文件的方式分爲主動和被動程序員


i.主動方式:
1.利用jmap,也是最經常使用的方式:jmap -dump:[live],format=b,file=
2.利用jcmd,jcmd GC.heap_dump
3.使用VisualVM,能夠界面操做進行dump內存
4.經過JMX的方式web

MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
mxBean.dumpHeap(filePath, live);複製代碼

參考(www.baeldung.com/java-heap-d…)
數組


ii.被動方式:
被動方式就是咱們一般的OOM事件了,經過設置參數-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=瀏覽器

dump文件分析

結構示意圖bash

結構詳解dom

dump文件是堆內存的映射,由文件頭和一系列內容塊組成jvm

文件頭ide

由musk, 版本,identifierSize, 時間4部分組成工具

一、musk:4個byte,內容爲'J', 'A', 'V', 'A'即JAVA

二、version:若干byte,值有如下三種

" PROFILE 1.0\0",
" PROFILE 1.0.1\0",
" PROFILE 1.0.2\0"複製代碼

三、identifierSize:4個byte數字,值爲4或者8,表示一個引用所佔用的byte數

四、time:8個byte,dump文件生成時間

說明:java一個類的成員變量有兩種類型

  1. 基本類型(8種基本類型),它們佔用byte數固定不變,每生成一個對象它們就須要給它們賦初始值,分配空間
  2. 是引用類型,表示一個對象,在類中只有一個引用,引用只是一個數值,所佔用的空間大小爲identifierSize,被引用對象即將在堆中的另外一個地方
    例如定義一個類
public class Person {
 private int age;//4個byte
 private String name;//identifierSize個byte
 private double weight;//8個byte
}複製代碼

當咱們在new Person()的時候
它就須要申請一個空間,空間大小爲 對象頭大小+4+identifierSize+8個byte

對象大小的測量:
jdk提供一個測試對象佔用內存大小的工具Instrumentation,可是Instrumentation無法直接引用到,須要經過agent來引用到
定義一個Premain類, javac Premain.java

//Premain.java
public class Premain {
    public static java.lang.instrument.Instrumentation inst;
    public static void premain(String args, java.lang.instrument.Instrumentation inst) {
        Premain.inst = inst;
    }
}複製代碼

編寫一個Manifest文件

manifest.mf
Manifest-Version: 1.0
Premain-Class: Premain
Can-Redefine-Classes: true
Can-Retransform-Classes: true複製代碼

打包

jar -cmf manifest.mf premain.jar Premain.class複製代碼

定義一個執行類, javac PersonTest.java

//PersonTest.java
public class PersonTest {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("Premain");
        if (clazz != null) {
            Person p = new Person();
            java.lang.instrument.Instrumentation inst = (java.lang.instrument.Instrumentation)clazz.getDeclaredField("inst").get(null);
            System.out.println("person size:[" + inst.getObjectSize(p) + "]B");
            System.out.println("class size:[" + inst.getObjectSize(p.getClass()) + "]B");
        }
    }
}複製代碼

帶agent執行

java -javaagent:premain.jar PersonTest複製代碼

結果:

person size:[32]B
class size:[504]B複製代碼

內容塊

每一個塊都是塊頭和塊體組成

塊頭

塊頭由1個byte的塊類型,4個byte的時間time,4個byte的長度表示此內容塊佔用byte數
type類型通常有5種,字符串,類,棧楨,棧,及dump塊

  1. 字符串,由identifierSize個byte的字符串id,後面是(length-identifierSize)個byte的字符串內容(後續對字符串是直接引用的這裏面的id)
  2. 類,由4個byte的類序列(在棧楨中使用),identifierSize個byte的類id(解析類的時候用到),4個byte的序列id(暫未使用),identifierSize個byte的類名id
  3. 棧楨,由identifierSize個byte的楨id,identifierSize個byte的方法名id,identifierSize個byte的方法標識id,identifierSize個byte的類文件名id,4個byte的類序列,4個byte的行號
  4. 棧,由4個byte的棧序號,4個byte的線程序號,4個byte的楨數量,後面就是若干個identifierSize個byte的楨id
  5. dump塊就是全部對象的內容了,每一個對象由1個byte的子類型,和對象內容結成,子類型有6種,gc root, 線程對象,類,對象,基本類型數組,對象數組

gc root

gc root有4種結構,8種類型

  1. identifierSize個byte的對象id,類型有SYSTEM_CLASS,BUSY_MONITOR, 及未UNKNOWN
  2. identifierSize個byte的對象id,4個byte的線程序列號,類型有NATIVE_STACK,THREAD_BLOCK
  3. identifierSize個byte的對象id,4個byte的線程序列號,4個byte的棧楨深度,類型有JAVA_LOCAL,NATIVE_LOCAL
  4. identifierSize個byte的對象id,identifierSize個byte的global refId(暫未使用),類型有NATIVE_STATIC

gc root示意圖

gc root爲垃圾收集追溯的源頭,每一個gc root都指向一個初始對象,沒法追溯的對象是要被回收掉的

系統類,只有classLoader爲null的類纔是gc root,每一個類都是一個gc root
線程棧,線程中方法參數,局部變量都是gc root,每一個對象都是一個gc root
系統保留對象,每一個對象都是一個gc root

類對象

一、基本信息:

  1. identifierSize個byte的類對象id
  2. 4個byte的棧序列號,
  3. identifierSize個byte的父類對象id,
  4. identifierSize個byte的classLoader對象id,
  5. identifierSize個byte的Signer對象id,
  6. identifierSize個byte的protection domain對象id,
  7. identifierSize個byte的保留id1和id2,
  8. 4個byte的類實例對象大小,
  9. 2個byte的常量個數,後面是每一個常量的,2個byte的下標,1個byte的常量類型,和若干個byte的內容,內容根據類型來決定(boolean/byte爲1個byte, char/short爲2個byte,float/int爲4個byte, double/long爲8個byte,引用類型爲identifierSize個byte)
  10. 2個byte的靜態變量個數,後面是每一個靜態變量的,identifierSize個byte的變量名id, 1個byte的變量類型,和若干個byte的內容,內容根據類型來決定(見類對象基本信息的第9條)
  11. 2個byte的成員變量個數,後面是每一個成員變量的,identifierSize個byte的變量名id,1個byte的變量類型

二、說明:
(1)類裏面的常量不少地方都沒有用上,因此常量個數通常爲0
(2)類的靜態變量的名稱類型及值是放在類對象裏面的,成員變量的名稱和類型也是放在類對象裏面的,可是實例的值是放在實例對象裏面的

實例對象

一、基本信息:

  1. identifierSize個byte的實例對象id
  2. 4個byte的棧序列號
  3. identifierSize個byte的類id
  4. 4個byte的佔用字節數
  5. 實例的變量的值

二、說明:

  1. 實例的值爲實例對象的成員變量值,順序爲當前類的變量值,順序爲類對象基本信息中第11條中的順序,而後是父類的變量值
  2. 變量的值基本類型都有默認值,引用類型默認值爲0,佔用字節數(見類對象基本信息的第9條)

基本類型數組

一、基本信息:

  1. identifierSize個byte的數組對象id
  2. 4個byte的棧序列號
  3. 4個byte的數組長度
  4. 1個byte的元素類型
  5. 元素的值列表

二、說明:

  1. 元素的值(見類對象基本信息的第9條)

對象數組

一、基本信息:

  1. identifierSize個byte的數組對象id
  2. 4個byte的棧序列號
  3. 4個byte的數組長度
  4. identifierSize個byte的元素類id
  5. 元素的值列表

內存分配

當一個線程啓動的時候,進程會去系統內存生成一個線程棧
每當發生一次方法調用,就會向棧中壓入一個棧楨,當方法調用完以後,棧楨會退出
在運行過程當中,若是有對象的new操做的時候,進程會去堆區申請一塊內存
關於運行時內存的詳細狀況,能夠查找相關的資料

內存回收規則

若是一個對象不能騎過gc root引用可達,那麼這個對象就可能要被回收
對象回收規則包括

  1. 實例屬性被實例引用,只有當實例被回收了實例屬性才能被回收(只針對強引用)
  2. 類對象被實例引用,只有當一個類的全部實例都被回收了,類才能被回收
  3. 類對象的父類,classLoader對象,signer對象, protection domain對象被類引用,只有當類被回收了,這些才能被回收
  4. 局部變量(線程棧中)的做用域爲一個大括號
public void test(){
Object a = new Object();//obj 1
Object b = new Object();//obj 2
{
Object c = new Object();//obj 3
a = null;//obj 1能夠被回收了
}//obj 3能夠回收了
}//obj 2能夠被回收了複製代碼

分析工具簡介

分析dump文件,咱們能夠用jdk裏面提供的jhat工具,執行

jhat xxx.dump複製代碼

jhat加載解析xxx.dump文件,並開啓一個簡易的web服務,默認端口爲7000,能夠經過瀏覽器查看內存中的一些統計信息

通常使用方法

一、瀏覽器打開http:/127.0.0.1:7000

會列出一些功能,包括package下面各個類的概覽,及各個功能導航


二、點擊頁面的堆內存統計

有一個表格,對象類型,實例個數,實例所佔用內存大小,哪一種類型的對象佔用了內存最多一目瞭然

三、點擊其中認爲內存消耗太多的類名查看類詳情

主要展示該類下面各個實例的大小,以及一些連接導航


四、點擊references summary by type

若是某種類型的對象太多,那麼有多是引用它的那個類的對象太多

基本上一些簡單頁面的查詢,結合原代碼,就能夠初步定位內存泄漏的地方

綜上,dump文件結構仍是比較簡單的,這對於分析線程的執行狀況很是有用,也是每個Java程序員必須掌握的高級技能之一,你學會了嗎?


點擊關注,第一時間瞭解華爲雲新鮮技術~

相關文章
相關標籤/搜索