前言:
很多时候,我们需要分析下当前进程的内存使用情况(OOM等异常)。
一般,我们会先使用命令打印出一个heap dump文件(文件会比较大),然后使用命令或工具来分析占用内存比较大的对象。
命令的话就是使用本文中要介绍的jhat命令,而关于工具一般使用MAT工具(下一篇文章中介绍)。
本次使用jhat工具主要完成三个小目标:
* 占用内存最大对象分析
* 对象具体内容分析
* OQL语句使用
1.准备工作
1.1 基本对象
public class Student implements Serializable {
private static final long serialVersionUID = -5423067787593230604L;
private Long id;
private String name;
private int age;
...
}
public class User implements Serializable {
private static final long serialVersionUID = -1754313838977971228L;
private String password;
private String username;
private byte[] bytes = null;
public User(String password, String username) {
this.password = password;
this.username = username;
// 在这里创建一个10M的数组
bytes = new byte[10 * 1024 * 1024];
}
...
}
1.2 创建对象添加到集合
@Controller
@RequestMapping("/jvmtest")
public class JvmTest {
// 模拟占用大内存
private List<Byte[]> list = new ArrayList<>();
// 以下三种模式集合信息
private List<Student> stuList = new ArrayList<>();
private Set<User> userSet = new HashSet<>();
private Map<Long,Student> stuMap = new HashMap<>();
@RequestMapping(path = "/addObj", method = RequestMethod.GET)
public String addObj() {
int i = 0;
for (int j = 0; j < 10; j++) {
userSet.add(new User("jack" + i, "pass" +i));
Student student = new Student(Long.valueOf(i + 1), "lucy" + i, i);
stuList.add(student);
stuMap.put(student.getId(), student);
}
return "success";
}
@RequestMapping(path = "/full", method = RequestMethod.GET)
public String full_test() {
Byte[] array2 = new Byte[2 * 1024 * 1024];
list.add(array2);
return "success";
}
}
多调用/jvmtest/full 请求,不断添加Byte[]到集合中,用于占用内存;
调用一次/jvmtest/addObj 请求,添加10个Student、10个User对象到各集合中;
1.3 产生heapdump文件
# 获取当前进程ID 为19832
PS D:\Program Files\Java\jdk1.8.0_131\bin> jps
3056
7648 Launcher
1828 RemoteMavenServer
19832 Bootstrap
19772 Jps
# 使用jmap命令产生当前进程的heapdump文件,命名为heapdump.phrof
PS D:\Program Files\Java\jdk1.8.0_131\bin> .\jmap.exe -dump:format=b,file=heapdump.phrof 19832
Dumping heap to D:\Program Files\Java\jdk1.8.0_131\bin\heapdump.phrof ...
Heap dump file created
2.jhat命令分析heapdump文件
2.1 jhat命令产生进程heapdump文件
# jhat 后面添加-J(可配置jvm参数,避免heapdump文件占用过大内存)
PS D:\Program Files\Java\jdk1.8.0_131\bin> .\jhat.exe -J-mx512M .\heapdump.phrof
Reading from .\heapdump.phrof...
Dump file created Mon Mar 21 11:45:07 GMT+08:00 2022
Snapshot read, resolving...
Resolving 261348 objects...
Chasing references, expect 52 dots....................................................
Eliminating duplicate references....................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
启动一个端口为7000的http服务,我们直接来访问来地址
2.2 查看HTML
通过访问http://localhost:7000
我们先来整体看下各个部分的含义
2.2.1 All Classes (excluding platform)
展示所有类信息,不包含平台相关类信息
All Classes (excluding platform) # 这里展示的所有的class信息
Package <Arrays> # 这里的类所在包名
class [Ljavax.servlet.DispatcherType; [0xc02ac410] # 这里是具体类信息
class [Ljavax.servlet.FilterConfig; [0xc0811490]
class [Ljavax.servlet.SessionTrackingMode; [0xc0552d40]
class [Ljavax.servlet.jsp.JspContext; [0xfe133108]
class [Ljavax.servlet.jsp.JspWriter; [0xfe133038]
class [Ljavax.servlet.jsp.PageContext; [0xfe1330a0]
class [Ljavax.servlet.jsp.tagext.BodyContent; [0xfe132fd0]
class [Ljavax.servlet.jsp.tagext.VariableInfo; [0xc0811358]
class [Ljavax.websocket.CloseReason$CloseCode; [0xc0811b78]
class [Ljavax.websocket.CloseReason$CloseCodes; [0xc0811b10]
来看下我们的com.example.domain.User类信息
All Classes (excluding platform)
...
Package com.example.domain
class com.example.domain.Student [0xedeaa128]
class com.example.domain.User [0xedea5ce0] # 类信息在这里,唯一的一个类
2.2.2 All Classes (including platform)
展示所有类信息,包含平台相关类信息
All Classes (including platform)
Package <Arrays>
class [B [0xc001a530]
class [C [0xc001ba98]
class [D [0xc0884e78]
class [F [0xc0884ee0]
class [I [0xc0049c40]
class [J [0xc07c23e0]
class [Lcom.sun.beans.util.Cache$CacheEntry; [0xfe17b0b8]
class [Lcom.sun.beans.util.Cache$Kind; [0xfe17b188]
class [Lcom.sun.jmx.mbeanserver.ClassLoaderRepositorySupport$LoaderEntry; [0xc08765c8]
class [Lcom.sun.jmx.mbeanserver.MXBeanMapping; [0xc0874150]
class [Lcom.sun.jmx.remote.security.MBeanServerFileAccessController$AccessType; [0xc0858d08]
...
与2.2.1基本没有啥区别
2.2.3 All Members of the Rootset
从根集能引用到的类(笔者也没太明白啥意思)
All Members of the Rootset
Java Static References
Static reference from com.sun.beans.TypeResolver.CACHE (from class com.sun.beans.TypeResolver) :
--> com.sun.beans.WeakCache@0xc0ef73d0 (24 bytes)
Static reference from com.sun.beans.finder.BeanInfoFinder.DEFAULT (from class com.sun.beans.finder.BeanInfoFinder) :
--> sun.beans.infos (28 bytes)
Static reference from com.sun.beans.finder.BeanInfoFinder.DEFAULT_NEW (from class com.sun.beans.finder.BeanInfoFinder) :
--> com.sun.beans.infos (28 bytes)
...
2.2.4 Instance Counts for All Classes (including platform)
这里是所有类的实例信息
Instance Counts for All Classes (including platform)
54597 instances of class [C
50177 instances of class java.lang.String # 50177代表当前实例的数量
14590 instances of class java.util.HashMap$Node
11517 instances of class java.util.concurrent.ConcurrentHashMap$Node
9611 instances of class java.lang.reflect.Method
8234 instances of class [Ljava.lang.Class;
7504 instances of class java.lang.Object
6642 instances of class [Ljava.lang.Object;
5800 instances of class java.lang.Class
4715 instances of class [B
这里按照实例数量倒排序。
我们自行创建的User实例也在其中展示
...
10 instances of class com.example.domain.Student
10 instances of class com.example.domain.User
2.2.5 Heap Histogram
对象占用堆内存 柱状图
通过这个可以比较好的分析出占用内存最大的对象信息。
可以看到Byte占用内存最大。点击进去可以看到具体的byte对象信息
我们反过来通过User对象来验证下byte[]是否在上述references里
点击进去,
我们创建的10个User对象,已经在这,随便点击一个进去
可以很清楚看到,当前User对象有三个属性,byte[],password=jack9,username=pass9
而数组对象0xf868bd98(占用内存为10M),在上述byte[]对象(占用内存最大的块里也可以找到)
3.OQL使用
OQL是jhat支持的一种语言,类似于SQL,可以对堆内存中的对象进行查询。
语法还是有点复杂的,具体大家可以参考下别人的文档。
4.奇怪的知识点
上面在展示heap 的时候,[B 代表什么呢?关于这些标志的全解释如下:
[Z = boolean
[B = byte
[S = short
[I = int
[J = long
[F = float
[D = double
[C = char
[L = any non-primitives(Object)