当前位置: 首页 > 科技观察

Java内存分析工具MAT详解

时间:2023-03-18 02:55:49 科技观察

这是阅读MAThelper的笔记。Heapdump是java进程在特定时间的内存快照。通常,在触发heapdump之前会进行一次fullgc,这样dump的内容就包含了gc之后的对象。转储文件的内容:1.所有对象:类、域、本机值和引用;2.所有类:类加载器、类名、超类、静态域;3.GCroot:JVM定义的reachable4.线程栈和局部变量:线程的调用栈,以及局部对象每一帧的信息。dump文件不包含内存分配信息,因此无法查询到谁创建了哪个对象等信息。浅堆是一个对象占用的内存空间,一个对象需要32位或者64位。RetainedsetofX是X在被jvmgc回收后被移除的一组对象。X的retainedheap是X的retainedset中所有对象的shallowheapsize的总和。换句话说,就是让X保持存活所需的内存空间。通俗地说,shallowheap就是一个对象在内存中的实际空间,而retainedheap就是一个对象被gc回收后从内存中释放出来的空间。这张图可以理解什么是leadingset,什么是retainedset。支配树:定义一个对象x支配对象y,当从root到y的每条路径都经过x。说白了,只要有y对象活着,就一定有x对象。支配树是将对象引用图转换成的树结构。帮助发现保持活动状态的对象之间的依赖关系,并识别保留内存的最佳块。y的直接支配者x是最接近y的支配者。支配树有几个属性:1、对象x的子树中包含的对象(x支配的对象集),表示x的保留集;2、如果x是y的直接支配者,则x的直接支配者也支配y,以此类推;3.支配树中的边并不代表对象引用图中对应的边,严格来说不属于直接对象引用。此图反映了将对象引用图转换为支配树的示例。Gcroot:gcroot是一个可以从堆外访问和读取的对象。下面是一些使对象成为GC根的方法。1、系统类:Bootstrap或系统类加载器加载的类,如rt.jar中的java.util.*;2、JNIlocal:native代码中的局部变量,如用户定义的JNI代码和JVMinternalsCode;3、JNIglobal:本机代码中的全局变量;4、线程块:当前活动线程块中引用的对象;5、Thread:已经启动还没有停止的线程;6、busymonitor:wait被调用()或notify()或synchronized同步的对象,如果是synchronized方法,则静态方法指的是类,非静态方法指的是对象;7、javalocal:局部变量,如方法输入、方法创建等8、nativestack:native代码中的输入输出参数,如file/net/IO方法、反射参数等;9、finalizable:等待其终结器在队列中运行的对象;10、unfinalized:一个对象用finalize方法的对象还没有被finalize,还没有进入finalizer队列等待finalization;11、unreachable:将不会被触及的对象在MAT中标记为root保留该对象,否则不会被解析出现;12、java栈帧:java栈帧包含局部变量。当dump被解析,stackframe被设置为preferences中的对象时,此时会生成;13、未知:位置的根类型。接下来是一些获取dump的方法:1、oom中dump:JVM参数:-XX:+HeapDumpOnOutOfMemoryError2、交互式环境dump:1)JVM参数:-XX:+HeapDumpOnCtrlBreak2)使用外部工具:jmap-dump:format=b,file=3)使用外部工具:jconsole4)使用外部工具:MAT5)kill-36)jstack-l>一些排错方法:1.查找大对象通过顶级消费者,并根据类、类加载器和包进行分组;2.通过immediatedominator查找责任对象,对于快速定位一组对象的持有者非常有用。这个操作直接解决了“谁让这些对象还活着”的问题,而不是“谁对这些对象有引用”的问题,更直接高效;3、运行类加载器分析,这个重要性体现在亮点上:***,应用程序使用不同的classloader来加载类,第一,第二,不同的classloader加载的class存放在不同的第一代,理论上也是可以循环使用的,当一个class被不同的classloader加载时,需要根据每个loader下的instance数量判断哪个loader更重要,从而回收另一个;4.分析线程,heapdump本身包含线程信息,可以通过MAT查看线程的概览和详细信息.细节包括线程堆内存信息,线程栈,操作系统的本地栈。假设我们不做heapdump,发现系统有问题,如何从线程的角度排查呢?首先top-H-p以线程方式查看java应用的运行状态,找到占用CPU或内存大的线程,记录线程id,然后printf%x转为16进制,然后jstack-l>thread.logdump出java进程的线程,从中找出tid,分析是哪个线程占用了系统资源。5、分析java容器类,因为java容器类是最常用来存储对象的,所以内存泄漏的风险理论上也是最高的。可以从几个角度来看:1)数组填充率查询(填充率是数组中非空元素的比例),打印非原生类型数组的填充率频率分布,从而查看利用率系统中的数组;2)Arraygroupquerybysize,打印一个按大小分组的直方图;3)集合填充率查询,ArrayList/HashMap/Hashtable/Properties/Vector/WeakHashMap/ConcurrentHashMap$Segment;4)按大小分类的集合组直方图;5)查看列表中的所有对象;6)查看hashmap中的所有对象;7)查看hashset中的对象;8)检查地图碰撞率;9)检查只有一个常量的所有数组。6、分析Finalizer,1)查询finalizer正在处理的对象;2)查询finalizer要处理的对象;3)直接检查finalizer线程;4)检查终结器线程的线程局部对象。