## JVM的内存分析神器
visualVM是甲骨文开发的一款JVM分析工具,可以分析JVM运行情况,GC情况,以及支持OOM分析,等强大功能。
官方网站:visualvm.github.io,下载地址:https://visualvm.g...
JVM内存溢出分析
内存dump
文件分析
要获取OOM时的内存dump文件,需要在JVM启动参数上新增两个参数,一个是开启dump,另一个是文件保存位置。
dump文件后缀一般为.hprof
,如果是这种文件的话一般来说就是JVM
内存dump
文件了。
第一步,将文件复制到本地
从服务器下载.horof
文件到本地,下载方法不在赘述,k8s
、docker
、原生linux
部署,下载方式可能会有差异,具体请Google
下载完成后你就会得到一个体积挺大的文件,我这个文件是直接使用visualVM
进行dump的,放心,和OOM自动dump的是一样的。
第二步,使用visualVM
打开文件
打开visualVM
,点击File
再点击Load
等待加载完成后会打开一个默认的界面:Summary
界面
这个界面包含了JVM的一些列信息:
- 堆内存信息:堆大小、加载的类、类实例、GC Roots、等..
- JVM环境信息:系统版本、OS平台、JavaHome地址、Java版本、虚拟机厂商等...
- JVM启动参数,需要点击:
System Properties [show]
才可以展示 - 类实例的具体个数、类实例对应的大小
- 等...
我们先看Class by Number Of Instances
,这块展示的类实例的具体信息,我们可以得到以下结果:
Object
类在堆中共存在,157705180
,1.5亿个,占比100%
char[]
数组在堆中共存在:9312个,占比0%
这不用往下看了,Object类实例明显不正常,什么代码会创建这么多Object。
我们直接双击Object,打开具体有哪些Object实例。
在这个界面我们可以看到所有的Object
,Instance
,因为太多展示不完,所以最后一行就会显示:另外还有157705080个instances
,点击就能查看更多
我们的目的就是要找到这些对象不能被回收的原因是什么,我们都知道在JVM里,如果一个对象的GC Root
还在,那它就不能被回收,所以我们点击工具栏的GC Root
按钮,查看这些对象的GC Root
是谁
我们可以随便多点几个,结果如表格所示
对象实例 | GC ROOT | 图片 |
---|---|---|
java.lang.Object#5 | elementData in java.util.ArrayList#1 [GC root - Java frame] : 157,704,907 elements | |
java.lang.Object#6 | elementData in java.util.ArrayList#1 [GC root - Java frame] : 157,704,907 elements | |
java.lang.Object#7 | elementData in java.util.ArrayList#1 [GC root - Java frame] : 157,704,907 elements | |
java.lang.Object#8 | elementData in java.util.ArrayList#1 [GC root - Java frame] : 157,704,907 elements |
点击图片,或右击->在新页标签内查看,即可查看大图
可以了,以及不要点了,从GC ROOT
给到的数据中可以看到,Object对象的引用根,在一个名为:elementData
的对象里,elementData
属于ArrayList
,这个elementData
中存在1.5+
亿个Object,出问题的地方就在这了。
我们再次双击GC Root
选卡卡中的elementData
节点
点击<fields>
和<refernces>
左侧的加号即可查看详细信息。
我们可以从references
中看到,这个ArrayList
是个static
字段,名叫OOM_LIST
,处于cn.yufire.learing.OOMSample
类中。
从
<fields>
中可以看到类的具体信息,我们的类是ArrayList
所以就是ArrayList
内部的字段信息,可以看到内部已经操作157704908
次(modCount=157704908
)
我们找到对应位置的代码查看,究竟做了什么:
java
public class OOMSample {
private static List<Object> OOM_LIST = new ArrayList<>();
public static void main(String[] args) {
ThreadUtil.sleep(3000);
new Thread(new AddObjectToMemory()).start();
new Thread(new AddObjectToMemory()).start();
new Thread(new AddObjectToMemory()).start();
new Thread(new AddObjectToMemory()).start();
new Thread(new AddObjectToMemory()).start();
ThreadUtil.sleep(22222222222L);
}
public static class AddObjectToMemory implements Runnable {
@Override
public void run() {
while (true) {
synchronized (OOMSample.class) {
OOM_LIST.add(new Object());
}
}
}
}
}
原来是一个死循环在疯狂执行new Object()
,OOM_LIST.add()
这两个操作,怪不得堆中会有1.5亿+个对象,并且不能被回收,因为OOM_LIST
是一个static
对象,本身就不会被回收,并且也没有地方将其设置为null
,就会导致GC回收不掉了。
剩下的就是修改代码逻辑,优化对象的创建了。
我这个
.hprof
文件是自己dump下来的,真实生产环境会比这个复杂的多,对象也会比较复杂,如果你对项目特别熟悉的话,就不会错过visualVM
给到你的每一个细节,如果你对项目不熟悉,就算给到你了一个类路径,你也不一定能注意的到这是问题的产生点,这块是非常需要注意的。