干货分享丨jvm系列dump文件深度分析

摘要:java内存dump是jvm运行时内存的一份快照,利用它能够剖析是否存在内存节约,能够查看内存治理是否正当,当产生OOM的时候,能够找出问题的起因。那么dump文件的内容是什么样的呢?

JVM dump

java内存dump是jvm运行时内存的一份快照,利用它能够剖析是否存在内存节约,能够查看内存治理是否正当,当产生OOM的时候,能够找出问题的起因。那么dump文件的内容是什么样的呢?咱们一步一步来

获取JVM dump文件

获取dump文件的形式分为被动和被动

i.被动形式:
1.利用jmap,也是最罕用的形式:jmap -dump:[live],format=b,file=
2.利用jcmd,jcmd GC.heap_dump
3.应用VisualVM,能够界面操作进行dump内存
4.通过JMX的形式

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

参考(https://www.baeldung.com/java…

ii.被动形式:
被动形式就是咱们通常的OOM事件了,通过设置参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=

dump文件剖析

构造示意图

构造详解

dump文件是堆内存的映射,由文件头和一系列内容块组成

文件头

由musk, 版本,identifierSize, 工夫4局部组成

1、musk:4个byte,内容为’J’, ‘A’, ‘V’, ‘A’即JAVA

2、version:若干byte,值有以下三种

" PROFILE 1.0\0",
" PROFILE 1.0.1\0",
" PROFILE 1.0.2\0"

3、identifierSize:4个byte数字,值为4或者8,示意一个援用所占用的byte数

4、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、根本信息:

  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的变量类型

2、阐明:
(1)类外面的常量很多中央都没有用上,所以常量个数个别为0
(2)类的动态变量的名称类型及值是放在类对象外面的,成员变量的名称和类型也是放在类对象外面的,然而实例的值是放在实例对象外面的

实例对象

1、根本信息:

  1. identifierSize个byte的实例对象id
  2. 4个byte的栈序列号
  3. identifierSize个byte的类id
  4. 4个byte的占用字节数
  5. 实例的变量的值

2、阐明:

  1. 实例的值为实例对象的成员变量值,程序为以后类的变量值,程序为类对象根本信息中第11条中的程序,而后是父类的变量值
  2. 变量的值根本类型都有默认值,援用类型默认值为0,占用字节数(见类对象根本信息的第9条)

根本类型数组

1、根本信息:

  1. identifierSize个byte的数组对象id
  2. 4个byte的栈序列号
  3. 4个byte的数组长度
  4. 1个byte的元素类型
  5. 元素的值列表

2、阐明:

  1. 元素的值(见类对象根本信息的第9条)

对象数组

1、根本信息:

  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,能够通过浏览器查看内存中的一些统计信息

个别应用办法

1、浏览器关上http:/127.0.0.1:7000

会列出一些性能,包含package上面各个类的概览,及各个性能导航

2、点击页面的堆内存统计

有一个表格,对象类型,实例个数,实例所占用内存大小,哪种类型的对象占用了内存最多高深莫测

3、点击其中认为内存耗费太多的类名查看类详情

次要展示该类上面各个实例的大小,以及一些链接导航

4、点击references summary by type

如果某种类型的对象太多,那么有可能是援用它的那个类的对象太多

基本上一些简略页面的查问,联合原代码,就能够初步定位内存透露的中央

综上,dump文件构造还是比较简单的,这对于剖析线程的执行状况十分有用,也是每一个Java程序员必须把握的高级技能之一,你学会了吗?

点击关注,第一工夫理解华为云陈腐技术~

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理