深度分析java dump文件

发布时间:2021-07-05 18:40 来源:脚本之家 阅读:0 作者:华为云开发者社区 栏目: 开发技术

目录

      JVM dump

      java内存dump是jvm运行时内存的一份快照,利用它可以分析是否存在内存浪费,可以检查内存管理是否合理,当发生OOM的时候,可以找出问题的原因。那么dump文件的内容是什么样的呢?我们一步一步来

      获取JVM dump文件

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

      主动方式:

      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);

      参考()

      被动方式:

      被动方式就是我们通常的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.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
      • 4个byte的栈序列号
      • identifierSize个byte的类id
      • 4个byte的占用字节数
      • 实例的变量的值

      2、说明:

      • 实例的值为实例对象的成员变量值,顺序为当前类的变量值,顺序为类对象基本信息中第11条中的顺序,
      • 然后是父类的变量值变量的值基本类型都有默认值,引用类型默认值为0,占用字节数(见类对象基本信息的第9条)

      基本类型数组

      基本信息

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

      说明

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

      对象数组

      基本信息:

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

      一般使用方法

      浏览器打开http:/127.0.0.1:7000

      会列出一些功能,包括package下面各个类的概览,及各个功能导航

      点击页面的堆内存统计

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

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

      主要展现该类下面各个实例的大小,以及一些链接导航

      点击references summary by type

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

      基本上一些简单页面的查询,结合原代码,就可以初步定位内存泄漏的地方

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

      以上就是深度分析java dump文件的详细内容,更多关于java dump文件的资料请关注脚本之家其它相关文章!

      免责声明:本站发布的内容(图片、视频和文字)以原创、来自互联网转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系QQ:712375056 进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。