【JVM内存医生🧑‍⚕️】内存分析,OOM分析,🧑‍⚕️:让我来帮你瞧瞧看是哪不对劲~

## JVM的内存分析神器

visualVM是甲骨文开发的一款JVM分析工具,可以分析JVM运行情况,GC情况,以及支持OOM分析,等强大功能。

官方网站:visualvm.github.io,下载地址:https://visualvm.g...

JVM内存溢出分析

内存dump文件分析

要获取OOM时的内存dump文件,需要在JVM启动参数上新增两个参数,一个是开启dump,另一个是文件保存位置。

dump文件后缀一般为.hprof,如果是这种文件的话一般来说就是JVM内存dump文件了。

第一步,将文件复制到本地

从服务器下载.horof文件到本地,下载方法不在赘述,k8sdocker、原生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实例。

在这个界面我们可以看到所有的ObjectInstance,因为太多展示不完,所以最后一行就会显示:另外还有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给到你的每一个细节,如果你对项目不熟悉,就算给到你了一个类路径,你也不一定能注意的到这是问题的产生点,这块是非常需要注意的。

相关推荐
闲猫7 小时前
Spring AI 对接Deepseek ChatModel 聊天对话
java·前端·spring
葫芦和十三8 小时前
图解 MongoDB 28|块与迁移:balancer 怎么均衡数据
后端
葫芦和十三8 小时前
图解 MongoDB 27|分片策略:范围分片 vs 哈希分片
后端·mongodb·agent
paopaokaka_luck9 小时前
英语单词学习系统的设计与实现(基于艾宾浩斯遗忘曲线的智能复习机制、语音朗读、多题型测试与错题自动收录、Echarts图形化分析)
vue.js·spring boot·后端·echarts
自信的未来9 小时前
JSON 工具|Web Worker 工程化打包 + 语法自动修复 + 多语言代码生成实战
java·前端·json
Brookty9 小时前
【JavaEE】线程安全(一).4:写块串行保安全、CAS
java·开发语言·java-ee·多线程·线程安全
我叫黑大帅9 小时前
MySQL 并发插入竞态问题:原子写入实践指南
后端·mysql·面试
怕孤单的草丛10 小时前
缓存管理面临的主要问题
java·数据库·缓存
zhangxingchao10 小时前
AI大模型核心八:从 Agent Skill、长文档 RAG 到知识库更新与训练策略
前端·人工智能·后端