JVMdumpjava内存转储是jvm运行时内存的快照。可以用来分析是否存在内存浪费,检查内存管理是否合理,在出现OOM时找出问题的原因。那么转储文件的内容是什么样的呢?让我们逐步获取JVM转储文件。获取转储文件的方式分为主动和被动两种方式。主动方式:1、使用jmap,也是最常用的方式:jmap-dump:[live],format=b,file=2。使用jcmd,jcmdGC.heap_dump3.使用VisualVM,可以进行接口操作转储内存4.通过JMXMBeanServerserver=ManagementFactory.getPlatformMBeanServer();HotSpotDiagnosticMXBeanmxBean=ManagementFactory.newPlatformMXBeanProxy(server,"com.sun.management:type=HotSpotDiagnostic",HotSpotDiagnosticMXBean.class);mxBean.dumpHeap(文件路径,现场);参考(https://www.baeldung.com/java...ii.被动方式:被动方式就是我们常用的OOM事件,通过设置参数-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=dump文件分析结构图结构详解dump文件是堆内存的一个映射,由文件头和一系列内容块组成,文件头由四部分组成:musk、version、identifierSize、time1.musk:4字节,内容为'J','A','V','A'为JAVA2,version:几个字节,取值有以下三种:"PROFILE1.0\0","PROFILE1.0.1\0","PROFILE1.0.2\0"3.identifierSize:4个字节数,取值为4或8,表示一个引用占用的字节数4.time:8个字节,dump文件生成时间描述:一个ja??va类的成员变量有两个基本类型的类型(8种基本类型),其中占用的字节数是固定的。每生成一个对象,都需要给它们赋初值。分配的空间是一个引用类型,代表一个对象。类中只有一个引用,引用只是一个值。占用空间的大小为identifierSize。引用的对象将在堆中的另一个地方。比如定义一个类publicclassPerson{privateintage;//4byteprivateStringname;//identifierSizebyteprivatedoubleweight;//8byte}我们在newPerson()的时候,需要申请一个空间,这个空间的大小就是objectheader+4+identifierSize+8byteobjectsize测量:jdk提供了一个工具Instrumentation来测试对象占用的内存大小,但是Instrumentation不能直接引用,需要通过代理引用定义一个Premain类,javacPremain.java//Premain.javapublicclassPremain{publicstaticjava.lang.instrument.Instrumentationinst;publicstaticvoidpremain(Stringargs,java.lang.instrument.Instrumentationinst){Premain.inst=inst;}}编写Manifest文件manifest.mfManifest-Version:1.0Premain-Class:PremainCan-Redefine-Classes:trueCan-Retransform-Classes:truePackingjar-cmfmanifest.mfpremain.jarPremain.class定义了一个执行类,javacPersonTest.java//PersonTest.javapublicclassPersonTest{publicstaticvoidmain(String[]args)throwsException{Classclazz=Class.forName("Premain");if(clazz!=null){Personp=newPerson();java.lang.instrument.Instrumentationinst=(java.lang.instrument.Instrumentation)clazz.getDeclaredField("inst").get(null);System.out.println("personsize:["+inst.getObjectSize(p)+"]B");System.out.println("classsize:["+inst.getObjectSize(p.getClass())+"]B");}}}用代理执行java-javaagent:premain.jarPersonTest结果:personsize:[32]Bclasssize:[504]B内容块。每个区块由区块头和区块体组成。区块头由1个字节的区块类型、4个字节的时间time、4个字节的长度组成,表示该内容块的占用。字节数一般有5种类型,string,class,stackframe,stack,dumpblockstring,由一个identifierSize字节的字符串id组成,后面跟着一个(length-identifierSize)字节的字符串内容(后面的字符串是直接引用id)类,它由一个4字节的类序列(在堆栈帧中使用)、一个identifierSize字节的类id(在解析类时使用)和一个4字节的序列id(尚未使用)组成),identifierSize字节栈帧的类名id,由identifierSize字节的帧id,identifierSize字节的方法名id,identifierSize字节的方法标识id,identifierSize字节的类文件名id,4个字节的类序列,行numberstackof4bytes,stacknumberof4bytes,lineprogramnumberof4bytes,framenumberof4bytes,后面跟着几个identifierSizebyteframeiddumpblocks都是对象每个对象的内容由一个1字节的子类型和o对象内容。有6个子类型,gc根,线程对象,类,对象,基本类型数组,对象数组gcrootgcroot有4个结构体,8种identifierSizebytesobjectids,类型有SYSTEM_CLASS,BUSY_MONITOR,UNKNOWNnumber,4字节栈帧深度,类型JAVA_LOCAL,NATIVE_LOCAL目的。untraceable对象就是要回收的系统类。只有classLoader为null的类才是gc根,每个类都是一个gc根线程栈,线程中的方法参数,局部变量都是gc根,每个对象都是一个gc根系统保留对象,每个对象都是一个gc根类对象1.基本信息:identifierSize字节类对象id4字节栈序号,identifierSize字节父类对象id,identifierSize字节classLoader对象id,identifierSize字节Signer对象id,identifierSize字节保护域对象id,identifierSize字节保留id1andid2,4byte类实例的对象大小,2个字节的常量个数,后面跟着每个常量,2个字节的下标,1个字节的常量类型,几个字节的内容,内容是确定的根据类型(boolean/byte为1字节,char/short为2字节,float/int为4字节,double/long为8字节,引用类型为identifierSize字节)10.静态变量个数2字节,关注由每个静态变量编辑,identifierSize1个字节的变量名id,1个字节的变量类型,几个字节的内容,内容根据类型确定(见类对象基本信息第9条)11.2的成员变量个数bytes,下面为每个成员变量,identifierSize字节变量名id,1字节变量类型2。说明:(1)类中的常量很多地方用不到,所以常量个数一般为0(2)Class静态变量的名称类型和值放在类对象中,成员变量的名称和类型也放在类对象中,但是实例的值是实例对象1中的实例对象。基本信息:identifierSizeInstanceobjectidofbyte4bytestacksequencenumberidentifierSizeclassidofbyte4bytes实例变量值占用2.说明:实例的值为实例对象的成员变量值,顺序是当前类的变量值的变量值,顺序是类对象基本信息中第11项的顺序,然后是父类的变量值。基本信息第9条)基本类型数组1.基本信息:identifierSize字节数组对象id4字节栈序号4字节数组长度1字节元素类型元素值列表2.说明:元素值(见基本信息第9条类对象)对象数组1.基本信息:identifierSize字节的数组对象id,4字节的栈序号,identifierSize字节的数组长度,元素类id,元素值List内存分配当一个线程启动后,进程会去系统内存中生成一个线程栈。每当发生方法调用时,都会将一个栈帧压入栈中。当方法被调用时,栈帧将退出正在运行的进程。在进程中,如果有对象的new操作,进程会去堆区申请一块内存。关于运行时内存的详细信息,你可以找到相关资料。可以回收对象。回收规则包括实例属性被实例引用。只有当实例被回收时,实例属性才能被回收(只针对强引用)。类对象由实例引用。只有当一个类的所有实例都被回收该类才能被回收该类对象的父类,classLoader对象、signer对象和保护域对象由类引用。只有当类被回收时,这些才能被回收。局部变量(在线程栈中)的作用域是一个大括号publicvoidtest(){Objecta=newObject();//obj1Objectb=newObject();//obj2{Objectc=newObject();//obj3a=null;//obj1可以回收}//obj3可以回收}//obj2可以回收分析工具介绍分析dump文件,我们可以使用jdk中提供的jhat工具执行jhatxxx.dumpjhat加载分析xxx.dump文件,并开启一个简单的web服务,默认端口为7000,可以通过浏览器查看内存中的一些统计信息一般使用方法1。浏览器打开http://127.0.0.1:7000,会列出一些功能,包括包下各个类的概览和各个功能的导航。占用内存的大小,哪种类型的对象占用内存最多一目了然3、点击被认为消耗过多内存的类名,可以查看类详情。主要显示类下各个实例的大小,以及一些链接导航4.点击referencessummarybytype如果某个类型的对象过多,可能是该类的对象过多引用它。基本上,一些简单的页面查询,结合原来的代码,就可以初步定位到内存泄漏。综上所述,转储文件结构还是比较简单的,这对于分析线程的执行情况非常有用,也是每个Java程序员必须掌握的高级技能之一。你学会了吗?
