摘要: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 一个类的成员变量有两种类型
- 根本类型(8 种根本类型),它们占用 byte 数固定不变,每生成一个对象它们就须要给它们赋初始值,调配空间
- 是援用类型,示意一个对象,在类中只有一个援用,援用只是一个数值,所占用的空间大小为 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 块
- 字符串,由 identifierSize 个 byte 的字符串 id,前面是 (length-identifierSize) 个 byte 的字符串内容(后续对字符串是间接援用的这外面的 id)
- 类,由 4 个 byte 的类序列(在栈桢中应用),identifierSize 个 byte 的类 id(解析类的时候用到),4 个 byte 的序列 id(暂未应用),identifierSize 个 byte 的类名 id
- 栈桢,由 identifierSize 个 byte 的桢 id,identifierSize 个 byte 的办法名 id,identifierSize 个 byte 的办法标识 id,identifierSize 个 byte 的类文件名 id,4 个 byte 的类序列,4 个 byte 的行号
- 栈,由 4 个 byte 的栈序号,4 个 byte 的线程序号,4 个 byte 的桢数量,前面就是若干个 identifierSize 个 byte 的桢 id
- dump 块就是所有对象的内容了,每个对象由 1 个 byte 的子类型,和对象内容结成,子类型有 6 种,gc root, 线程对象,类,对象,根本类型数组,对象数组
gc root
gc root 有 4 种构造,8 种类型
- identifierSize 个 byte 的对象 id,类型有 SYSTEM_CLASS,BUSY_MONITOR, 及未 UNKNOWN
- identifierSize 个 byte 的对象 id,4 个 byte 的线程序列号,类型有 NATIVE_STACK,THREAD_BLOCK
- identifierSize 个 byte 的对象 id,4 个 byte 的线程序列号,4 个 byte 的栈桢深度,类型有 JAVA_LOCAL,NATIVE_LOCAL
- 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
- 4 个 byte 的栈序列号,
- identifierSize 个 byte 的父类对象 id,
- identifierSize 个 byte 的 classLoader 对象 id,
- identifierSize 个 byte 的 Signer 对象 id,
- identifierSize 个 byte 的 protection domain 对象 id,
- identifierSize 个 byte 的保留 id1 和 id2,
- 4 个 byte 的类实例对象大小,
- 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)
- 2 个 byte 的动态变量个数,前面是每个动态变量的,identifierSize 个 byte 的变量名 id, 1 个 byte 的变量类型,和若干个 byte 的内容,内容依据类型来决定(见类对象根本信息的第 9 条)
- 2 个 byte 的成员变量个数,前面是每个成员变量的,identifierSize 个 byte 的变量名 id,1 个 byte 的变量类型
2、阐明:
(1)类外面的常量很多中央都没有用上,所以常量个数个别为 0
(2)类的动态变量的名称类型及值是放在类对象外面的,成员变量的名称和类型也是放在类对象外面的,然而实例的值是放在实例对象外面的
实例对象
1、根本信息:
- identifierSize 个 byte 的实例对象 id
- 4 个 byte 的栈序列号
- identifierSize 个 byte 的类 id
- 4 个 byte 的占用字节数
- 实例的变量的值
2、阐明:
- 实例的值为实例对象的成员变量值,程序为以后类的变量值,程序为类对象根本信息中第 11 条中的程序,而后是父类的变量值
- 变量的值根本类型都有默认值,援用类型默认值为 0,占用字节数(见类对象根本信息的第 9 条)
根本类型数组
1、根本信息:
- identifierSize 个 byte 的数组对象 id
- 4 个 byte 的栈序列号
- 4 个 byte 的数组长度
- 1 个 byte 的元素类型
- 元素的值列表
2、阐明:
- 元素的值(见类对象根本信息的第 9 条)
对象数组
1、根本信息:
- identifierSize 个 byte 的数组对象 id
- 4 个 byte 的栈序列号
- 4 个 byte 的数组长度
- identifierSize 个 byte 的元素类 id
- 元素的值列表
内存调配
当一个线程启动的时候,过程会去零碎内存生成一个线程栈
每当产生一次办法调用,就会向栈中压入一个栈桢,当办法调用完之后,栈桢会退出
在运行过程中,如果有对象的 new 操作的时候,过程会去堆区申请一块内存
对于运行时内存的详细情况,能够查找相干的材料
内存回收规定
如果一个对象不能骑过 gc root 援用可达,那么这个对象就可能要被回收
对象回收规定包含
- 实例属性被实例援用,只有当实例被回收了实例属性能力被回收(只针对强援用)
- 类对象被实例援用,只有当一个类的所有实例都被回收了,类能力被回收
- 类对象的父类,classLoader 对象,signer 对象, protection domain 对象被类援用,只有当类被回收了,这些能力被回收
- 局部变量 (线程栈中) 的作用域为一个大括号
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 程序员必须把握的高级技能之一,你学会了吗?
点击关注,第一工夫理解华为云陈腐技术~