澳门新葡亰娱乐官网Java内存分析利器MAT使用详解

澳门新葡亰娱乐官网 11

这是一篇阅读MAT helper的笔记。Heap
dump是java进程在特定时间的一个内存快照。通常在触发heap
dump之前会进行一次full gc,这样dump出来的内容就包含的是被gc后的对象。

尽管JVM提供了自动内存管理的机制,试图降低程序员的开发门槛,确实也实现了这一目标,在日常开发中,我们一般都不需要关心对象的内存释放。JVM大部分都是使用trace算法来判断一个对象是否该被回收,那么JVM只能回收那些从gc
roots不可达的对象。

官方参考

Garbage Collection Roots
A garbage collection root is an object that is accessible from outside
the heap. The following reasons make an object a GC root:
System Class
Class loaded by bootstrap/system class loader. For example, everything
from the rt.jar like java.util.* .
JNI Local
Local variable in native code, such as user defined JNI code or JVM
internal code.
JNI Global
Global variable in native code, such as user defined JNI code or JVM
internal code.
Thread Block
Object referred to from a currently active thread block.
Thread
A started, but not stopped, thread.
Busy Monitor
Everything that has called wait() or notify() or that is synchronized.
For example, by calling synchronized(Object) or by entering a
synchronized method. Static method means class, non-static method
means object.
Java Local
Local variable. For example, input parameters or locally created
objects of methods that are still in the stack of a thread.
Native Stack
In or out parameters in native code, such as user defined JNI code or
JVM internal code. This is often the case as many methods have native
parts and the objects handled as method parameters become GC roots.
For example, parameters used for file/network I/O methods or
reflection.
Finalizable
An object which is in a queue awaiting its finalizer to be run.
Unfinalized
An object which has a finalize method, but has not been finalized and
is not yet on the finalizer queue.
Unreachable
An object which is unreachable from any other root, but has been
marked as a root by MAT to retain objects which otherwise would not be
included in the analysis.
Java Stack Frame
A Java stack frame, holding local variables. Only generated when the
dump is parsed with the preference set to treat Java stack frames as
objects.
Unknown
An object of unknown root type. Some dumps, such as IBM Portable Heap
Dump files, do not have root information. For these dumps the MAT
parser marks objects which are have no inbound references or are
unreachable from any other root as roots of this type. This ensures
that MAT retains all the objects in the dump

dump文件包含的内容:

1,全部的对象:类,域,原生值和引用;

2,全部的类:classloader,类名,超类,静态域;

3,GC root:被JVM定义的可触达的对象;

4,线程栈和本地变量:线程的call stack,本地对象每帧的信息。

dump文件不包含内存的分配信息,因此无法查询谁创建了哪个对象这样的信息。

Shallow heap是一个对象占用的内存空间,一个对象需要32或者64bits。

Retained set of X是X在被jvm gc回收后被remove的一组object。

Retained heap of X是在retained set of X中的所有对象的shallow heap
size的和。换句话说就是保持X活着需要的内存空间。

通俗的讲,shallow heap是一个对象在内存中的实际空间,而retained
heap是一个对象被gc回收后内存释放出来的空间。

澳门新葡亰娱乐官网 1

这张图可以看懂什么是leading set什么是retained set。

澳门新葡亰娱乐官网,Dominator tree:定义一个对象x dominate
对象y,当每一条从root开始到y的路径都经过x。说白了就是只要有y对象的存活,那么一定会有一个x对象。Dominator
tree就是将对象引用图转换成的树形结构。帮助发现在对象间保持alive的依赖,同时也能识别出retained内存的最大的chunk。
Immediate dominator x of y是离y最近的dominator。

Dominator tree有几个属性:

1,对象x的子树包含的对象(x dominate的对象集),代表了x的retained set;

2,如果x是y的immediate dominator,那么x的immediate dominator同样dominate
y,以此类推;

3,dominate
tree中的边不代表对象引用图里对应的边,并非严格的直接的对象引用。

澳门新葡亰娱乐官网 2

这张图反应了一个对象引用图转换成dominator tree的示例。

Gc
root:一个gc根就是一个对象,这个对象从堆外可以访问读取。以下一些方法可以使一个对象成为gc根。

1,System class:被Bootstrap或者system类加载器加载的类,比如rt.jar里的java.util.*;

2,JNI
local:native代码里的local变量,比如用户定义的JNI代码和JVM的内部代码;

3,JNI global:native代码里的global变量;

4,Thread block:当前活跃的线程block中引用的对象;

5,Thread:已经启动并且没有stop的线程;

6,busy
monitor:被调用了wait()或者notify()或者被synchronized同步的对象,如果是synchronized方法,那么静态方法指的类,非静态方法指的是对象;

7,java local:local变量,比如方法的入参和方法内创建的变量;

8,native
stack:native代码里的出入参数,比如file/net/IO方法以及反射的参数;

9,finalizable:在一个队列里等待它的finalizer 运行的对象;

10,unfinalized:一个有finalize方法的对象,还没有被finalize,同时也没有进入finalizer队列等待finalize;

11,unreachable:不会被触碰到的对象,在MAT里被标记为root用来retain
object,否则是不会在分析中出现的;

12,java stack
frame:java栈帧包含了本地变量,当dump被解析时且在preferences里设置过把栈帧当做对象,这时才会产生;

13,unknown:位置的root类型。

如果我们在使用某些大的对象、集合对象或者一些三方包里的资源,忘记及时释放资源的话,还是会造成JVM的内存泄漏或内存浪费的问题。因此,如果想成为更高阶的Java开发工程师,我们需要了解常见的问题排查的办法和工具,这个系列的文章,准备介绍一个用来做JVM堆内存分析的工具——MAT(Memory
Aanlysis Tool)。

关键点

  • 所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
  • VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot
    VM里的Universe里有很多这样的引用。
  • JNI handles,包括global handles和local handles
  • (看情况)所有当前被加载的Java类
  • (看情况)Java类的引用类型静态变量
  • (看情况)Java类的运行时常量池里的引用类型常量(String或Class类型)
  • (看情况)String常量池(StringTable)里的引用

接下来是一些获取dump的方法:

1,在oom时dump:JVM参数:-XX:+HeapDumpOnOutOfMemoryError

2,交互式环境下dump:

1)JVM参数:-XX:+HeapDumpOnCtrlBreak

2)用外部tools:jmap -dump:format=b,file=<filename.hprof>
<pid>

3)用外部tools:jconsole

4)用外部工具:MAT

5)kill -3 <pid>

6)jstack -l <pid> > <dumpfile>

MAT的官网在:

分代GC对GC roots的定义的影响

分代式GC是一种部分收集(partial
collection)的做法。在执行部分收集时,从GC堆的非收集部分指向收集部分的引用,也必须作为GC
roots的一部分。
具体到分两代的分代式GC来说,如果第0代叫做young gen,第1代叫做old
gen,那么如果有minor GC / young GC只收集young gen里的垃圾,则young
gen属于“收集部分”,而old gen属于“非收集部分”,那么从old gen指向young
gen的引用就必须作为minor GC / young GC的GC roots的一部分。
继续具体到HotSpot VM里的分两代式GC来说,除了old gen到young
gen的引用之外,有些带有弱引用语义的结构,例如说记录所有当前被加载的类的SystemDictionary、记录字符串常量引用的StringTable等,在young
GC时必须要作为strong GC roots,而在收集整堆的full
GC时则不会被看作strong GC roots。
换句话说,young GC比full GC的GC
roots还要更大一些。如果不能理解这个道理,那整个讨论也就无从谈起了。

一些排查方法:

1,通过top
consumers查找大对象,可以按照class、classloader和package进行group by;

2,通过immediate
dominator找到责任对象,对于快速定位一组对象的持有者非常有用,这个操作直接解决了“谁让这些对象alive”的问题,而不是“谁有这些对象的引用”的问题,更直接高效;

3,运行classloader分析,这个重要性体现在亮点:第一,应用使用不同的classloader加载类,第二,不同
classloader加载的类存储在不同的永久代,这理论上也是可以被回收的。当有一个类被不同的classloader加载时,这时要根据各自
loader下的instance数量判断哪个loader更重要,从而要把另一个回收掉;

4,分析线程,本身heap dump里包含了thread信息,可以通过MAT来查看threads
的overview和detail,detail中有线程的堆内存信息,也有线程栈,同时还包含了操作系统本地栈。假设不做heap
dump,我们检查到系统有问题,如何通过线程的角度来排查呢?首先top -H -p
<pid>以线程的模式查看java应用的运行情况,找到占用cpu或者内存大的线程,记录线程id,然后printf
%x <tid>转为16进制,再jstack -l <pid> >
thread.log把java进程的thread
dump出来,从里面找到tid,分析是哪个线程占用了系统资源。

5,分析java容器类,因为java的容器类是最常用来存储对象的,所以理论上发生内存泄露的风险也最高。可以从几个角度来
看:1)array填充率查询(填充率fill
ratio是数组中非空元素的比例),打印非原生类型数组的填充率频率分布,从而排查系统中array的利用率;2)数组按照size分组查询,打印一个
按size分组的直方图;3)collection的填充率查询,ArrayList/HashMap/Hashtable/Properties
/Vector/WeakHashMap/ConcurrentHashMap$Segment;4)collection按照size分组直方图;5)
查看一个list里的所有对象;6)查看hashmap里的所有对象;7)查看hashset里的对象;8)检查map的碰撞率;9)检查所有只有一个常
量的array。

6,分析Finalizer,1)查询finalizer正在处理的对象;2)查询finalizer准备处理的对象;3)直接查看finalizer线程;4)查看finalizer线程的thread
local对象。

1.1 mac安装

MAT 支持两种安装方式,一种是”单机版“的,也就是说用户不必安装 Eclipse IDE
环境,MAT 作为一个独立的 Eclipse RCP
应用运行;另一种是”集成版“的,也就是说 MAT 也可以作为 Eclipse IDE
的一部分,和现有的开发平台集成。

这里我们考虑独立安装,在观望的下载页面,选择mac
os版本的安装文件下载即可。

澳门新葡亰娱乐官网 3MAT的独立下载地址

  1. 启动直接报错,针对这个问题,我找到了这个答案:
    • 系统默认的workspace是只读的,更换掉即可。怎么更换呢,在文件/Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini中进行修改。澳门新葡亰娱乐官网 4image.png
  • 在mat的安装目录下,我的机器是/Applications/mat.app/Contents/MacOS,执行./MemoryAnalyzer命令,这种只能通过命令启动,不能通过图表启动。

关于方案1,这篇文章讲得更细致:

  1. 启动后,UI界面没反应,参考:

参考来源

  • java的gc为什么要分代?
  • gc
    roots有哪些?

1.2 mat的设置

我的电脑是8C16G的,那理论上分析10G的堆文件没问题,但是MAT默认的配置没有这么大,需要在/Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini文件中进行修改。如下图所示,我将我的MAT自己的运行时堆内存配置成了6G。

澳门新葡亰娱乐官网 5image.pngMAT的配置页面可以从Window——>Preferences找到,如下图所示。
澳门新葡亰娱乐官网 6image.png

MAT的一般配置有几个选项

  1. Keep unreachable
    objects:如果勾选这个,则在分析的时候会包含dump文件中的不可达对象;

  2. Hide the getting started
    wizard:隐藏分析完成后的首页,控制是否要展示一个对话框,用来展示内存泄漏分析、消耗最多内存的对象排序。

  3. Hide popup query
    help:隐藏弹出查询帮助,除非用户通过F1或Help按钮查询帮助。

  4. Hide Welcome screen on launch:隐藏启动时候的欢迎界面

  5. Bytes Display:设置分析结果中内存大小的展示单位

可以看出,MAT不仅支持HPROF文件的分析,还支持DTFJ文件的分析。一般sun公司系列的JVM生成的dump文件都是HPROF格式的,IBM的JVM生成的dump文件时DTFJ格式的。

唠叨一下

  • 完全属于面试题,对编程没有什么卵用

Heap Dump

Heap Dump是Java进程在某个时刻的内存快照,不同JVM的实现的Heap
Dump的文件格式可能不同,进而存储的数据也可能不同,但是一般来说。

Heap
Dump中主要包含当生成快照时堆中的java对象和类的信息,主要分为如下几类:

  • 对象信息:类名、属性、基础类型和引用类型

  • 类信息:类加载器、类名称、超类、静态属性

  • gc
    roots:JVM中的一个定义,进行垃圾收集时,要遍历可达对象的起点节点的集合

  • 线程栈和局部变量:快照生成时候的线程调用栈,和每个栈上的局部变量

Heap
Dump中没有包含对象的分配信息,因此它不能用来分析这种问题:一个对象什么时候被创建、一个对象时被谁创建的。

Shallow vs. Retained Heap

Shallow
heap
是一个对象本身占用的堆内存大小。一个对象中,每个引用占用8或64位,Integer占用4字节,Long占用8字节等等。

Retained set,对于某个对象X来说,它的Retained
set指的是——如果X被垃圾收集器回收了,那么这个集合中的对象都会被回收,同理,如果X没有被垃圾收集器回收,那么这个集合中的对象都不会被回收。

Retained heap,对象X的Retained heap指的时候它的Retained
set中的所有对象的Shallow si的和,换句话说,Retained
heap指的是对象X的保留内存大小,即由于它的存活导致多大的内存也没有被回收。

leading set,对象X可能不止有一个,这些对象统一构成了leading
set。如果leading set中的对象都不可达,那么这个leading set对应的retained
set中的对象就会被回收。一般有以下几种情况:

  1. 某个类的所有实例对象,这个类对象就是leading object
  2. 某个类记载器加载的所有类,以及这些类的实例对象,这个类加载器对象就是leading
    object
  3. 一组对象,要达到其他对象的必经路径上的对象,就是leading object

在下面这张图中,A和B是gc
roots中的节点(方法参数、局部变量,或者调用了wait()、notify()或synchronized等等。可以看出,E的存在,会导致G无法被回收,因此E的Retained
set是E和G;C的存在,会导致E、D、F、G、H都无法被回收,因此C的Retined
set是C、E、D、F、G、H;A和B的存在,会导致C、E、D、F、G、H都无法被回收,因此A和B的Retained
set是A、B、C、E、D、F、G、H。澳门新葡亰娱乐官网 7image.png

Dominator Tree

MAT根据堆上的对象引用关系构建了支配树(Dominator
Tree),通过支配树可以很方便得识别出哪些对象占用了大量的内存,并可以看到它们之间的依赖关系。

如果在对象图中,从gc
root或者x上游的一个节点开始遍历,x是y的必经节点,那么就可以说x支配了y(dominate)。

如果在对象图中,x支配的所有对象中,y的距离最近,那么就可以说x直接支配(immediate
dominate
)y。

支配树是基于对象的引用关系图建立的,在支配树中每个节点都是它的子节点的直接支配节点。基于支配树可以很清楚得看到对象之间的依赖关系。

现在看个例子,在下面这张图中

  1. x节点的子树就是所有被x支配的节点集合,也正式x的retained set;
  2. 如果x是y的直接支配节点,那么x的支配节点也可以支配y
  3. 支配树中的边跟对象引用图中的引用关系并不是一一对应的。

澳门新葡亰娱乐官网 8image.png

Garbage Collection Roots

在MAT中,gc roots的概念跟研究垃圾收集算法时候的概念稍微有点不同。gc
roots中的对象,是指那些可以从堆外访问到的对象的集合。如果一个对象符合下面这些场景中的一个,就可以被认为是gc
roots中的节点:

  1. System Class:由bootstrap
    classloader加载的类,例如rt.jar,里面的类的包名都是java.util.*开头的。
  2. JNI
    Local:native代码中的局部变量,例如用户编写的JNI代码或JVM内部代码。
  3. JNI
    Global:native代码中的全局变量,例如用户编写的JNI代码或JVM内部代码。
  4. Thread Block:被当前活跃的线程锁引用的对象。
  5. Thread:正在存活的线程
  6. Busy
    Monitor:调用了wait()、notify()或synchronized关键字修饰的代码——例如synchronizedsynchronized方法。
  7. Java
    Local:局部变量。例如函数的输入参数、正在运行的线程栈里创建的对象。
  8. Native
    Stack:native代码的输入或输出参数,例如用户定义的JNI代码或JVM的内部代码。在文件/网络IO方法或反射方法的参数。
  9. Finalizable:在finalize队列中等待它的finalizer对象运行的对象。
  10. Unfinalized:重载了finalize方法,但是还没有进入finalize队列中的对象。
  11. Unreachable:从任何gc
    roots节点都不可达的对象,在MAT中将这些对象视为root节点,如果不这么做,就不能对这些对象进行分析。
  12. Java Stack
    Frame:Java栈帧,用于存放局部变量。只在dump文件被解析的时候会将java
    stack frame视为对象。
  13. Unknown:没有root类型的对象。有些dump文件(例如IBM的Portable Heap
    Dump)没有root信息。

  1. 通过MAT生成dump文件通过这个路径找到生成dump文件的对话框

    澳门新葡亰娱乐官网 9image.png选择一个进程,点击finish即可
    澳门新葡亰娱乐官网 10image.png

  2. 通过jmap命令生成dump文件

    • 命令格式:jmap -dump:live,format=b,file=heap.bin <pid>
    • 注意:如果要保留heapdump中的不可达对象,则需要把”:live“去掉,即使用命令”jmap
      -dump,format=b,file=heap.bin <pid>“
  3. 通过设置JVM参数自动生成使用-XX:+HeapDumpOnOutOfMemoryError这个JVM参数,在Java进程运行过程中发生OOM的时候就会生成一个heapdump文件,并写入到指定目录,一般用-XX:HeapDumpPath=${HOME}/logs/test来设置。

本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。

澳门新葡亰娱乐官网 11javaadu

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图