乐趣区

干货分享丨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 程序员必须把握的高级技能之一,你学会了吗?

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

退出移动版