摘要: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.javapublic 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.mfManifest-Version: 1.0Premain-Class: PremainCan-Redefine-Classes: trueCan-Retransform-Classes: true
打包
jar -cmf manifest.mf premain.jar Premain.class
定义一个执行类, javac PersonTest.java
//PersonTest.javapublic 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]Bclass 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 1Object b = new Object();//obj 2{Object c = new Object();//obj 3a = 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程序员必须把握的高级技能之一,你学会了吗?
点击关注,第一工夫理解华为云陈腐技术~