使用MAT分析OOM问题

news2025/1/21 2:51:22

OOM和内存泄漏在我们的工作中,算是相对比较容易出现的问题,一旦出现了这个问题,我们就需要对堆进行分析。

一般情况下,我们生产应用都会设置这样的JVM参数,以便在出现OOM时,可以dump出堆内存文件,也就是保留案发现场,方便我们后续的研究。

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=.

至于分析堆内存的工具可以使用Jvisualvm,但Jvisualvm只能查看类使用内存的直方图,无法有效的追踪内存的引用关系,因此更加推荐使用Eclipse 的 Memory Analyzer(也叫做 MAT)做堆转储的分析。可以通过这个链接,下载 MAT。

使用MAT分析OOM问题,一般可以按照以下的思路进行:

  1. 通过支配树功能或直方图功能查看消耗内存最大的类型,来分析内存泄露的大概原因;
  2. 查看那些消耗内存最大的类型、详细的对象明细列表,以及它们的引用链,来定位内存泄露的具体点;
  3. 配合查看对象属性的功能,可以脱离源码看到对象的各种属性的值和依赖关系,帮助我们理清程序逻辑和参数;
  4. 辅助使用查看线程栈来看 OOM 问题是否和过多线程有关,甚至可以在线程栈看到 OOM 最后一刻出现异常的线程。

接下来,我们有一个案例,通过这个案例可以得到一个OOM后的堆转储文件java_pid12300.hprof,然后我们通过MAT的直方图、支配树、线程栈、OQL 等功能来分析此次 OOM 的原因。

在文章的最后会有代码地址,运行代码一段时间发生OOM后,你就可以得到一个hprof文件。

1、查看堆概述信息

通过MAT打开java_pid12300.hprof文件后,首先进入的是概览信息界面。
在这里插入图片描述
从这个概览图中,我们可以看出整个堆的大小是437.6MB。接下来我们可以通过直方图来看这437.6MB的对象都是哪些对象。

2、直方图观察对象分布

点击工具栏的第二个图标,进入到直方图视图
在这里插入图片描述
从直方图中,我们可以看到,char[]字节数组占用的内存最多,对象数量给也最多。排名第二的String对象也很多,可以推断程序可能是被String占满了(String底层使用的就是char[]作为实际存储,因此String多,char[]也会多)

3、分析char[]的引用关系

在 char[]上点击右键,选择 List objects->with incoming references,可以列出所有的char[]实例,以及每个 char[]的整个引用关系链:
在这里插入图片描述
随机展开一个 char[],如下图所示:
在这里插入图片描述

  • 在①处看到,这些 char[]几乎都是 10000 个字符、占用 20000 字节左右(char 是 UTF-16,每一个字符占用 2 字节);
  • 在②处看到,char[]被 String 的 value 字段引用,说明 char[]来自字符串;
  • 在③处看到,String 被 ArrayList 的 elementData 字段引用,说明这些字符串加入了一个 ArrayList 中;
  • 在④处看到,ArrayList 又被 FooService 的 data 字段引用,这个 ArrayList 整个 RetainedHeap 列的值是 431MB。

Retained Heap(深堆)代表对象本身和对象关联的对象占用的内存,Shallow Heap(浅堆)代表对象本身占用的内存。比如,我们的 FooService 中的 data 这个 ArrayList 对象本身只有 16 字节,但是其所有关联的对象占用了 431MB 内存。这些就可以说明,肯定有哪里在不断向这个 List 中添加 String 数据,导致了 OOM。

左侧的蓝色框可以查看每一个实例的内部属性,图中显示 FooService 有一个 data 属性,类型是 ArrayList。

如果我们希望看到字符串完整内容的话,可以右键选择 Copy->Value,把值复制到剪贴板或保存到文件中:
在这里插入图片描述
这里,我们复制出的是 10000 个字符 a(下图红色部分可以看到)。对于真实案例,查看大字符串、大数据的实际内容对于识别数据来源,有很大意义:
在这里插入图片描述

4、利用支配树查看内存中最大的对象

点击工具栏的第三个按钮可以进入到支配树界面,这个界面会根据Retained Heap 倒序直接列出占用内存最大的对象。
在这里插入图片描述
这样我们就可以很快速的定位到是哪个对象导致的OOM,接下来我们就要看一下OOM的时候,FooService在执行什么逻辑。

5、查看线程视图

点击工具栏的第五个按钮,打开线程视图,首先看到的是main线程。
在这里插入图片描述
从黑色框来看,确实这里发生了OOM。紧接继续往下看,寻找我们可以的FooService,可以看到这个线程栈中FooSerice.oom()方法被调用。

在往下看的话,可看到参数中的 CommandLineRunner 你应该能想到,OOMApplication 其实是实现了 CommandLineRunner 接口,所以是 SpringBoot 应用程序启动后执行的。

在FooService.oom()往上看,红色框部分,我们可以猜测出这些字符串是由Stream操作产生的,以及在上面的StringBuilder 的 append是最终导致OOM的方法。

6、OQL查找类

最后我们还可以看一下FooService是不是Spring的Bean,又是不是单例?如果是的话,就更能确定是因为反复调用同一个 FooService 的 oom 方法,然后导致其内部的 ArrayList 不断增加数据的。

我们可以点击工具栏的第四个按钮,进入到OQL界面,然后在这里我们可以使用类似 SQL 的语法,在 dump 中搜索数据(你可以直接在 MAT 帮助菜单搜索 OQL Syntax,来查看 OQL 的详细语法)。

比如,输入如下语句搜索 FooService 的实例:

select * from fcp.troubleshootingtools.mat.FooService

可以看到只有一个实例,然后我们通过 List objects 功能搜索引用 FooService 的对象:
在这里插入图片描述
得到以下结果:
在这里插入图片描述可以看到,一共两处引用:

  • 第一处是,OOMApplication 使用了 FooService,这个我们已经知道了。
  • 第二处是一个 ConcurrentHashMap。可以看到,这个 HashMap 是 DefaultListableBeanFactory 的 singletonObjects 字段,可以证实 FooService 是 Spring 容器管理的单例的 Bean。

我们甚至可以在HashMap 上点击右键,选择 Java Collections->Hash Entries 功能,来查看其内容:
在这里插入图片描述
在这里插入图片描述
我们还可以在Value列通过正则进一步对解决进行过滤筛选:
在这里插入图片描述
到现在为止,我们虽然没看程序代码,但是已经大概知道程序出现 OOM 的原因和大概的调用栈了。我们再贴出程序来对比一下,果然和我们看到得一模一样:

@SpringBootApplication
public class OOMApplication implements CommandLineRunner {
    @Autowired
    FooService fooService;
    public static void main(String[] args) {
        SpringApplication.run(OOMApplication.class, args);
    }
    @Override
    public void run(String... args) throws Exception {
        //程序启动后,不断调用Fooservice.oom()方法
        while (true) {
            fooService.oom();
        }
    }
}
@Component
public class FooService {
    List<String> data = new ArrayList<>();
    public void oom() {
        //往同一个ArrayList中不断加入大小为10KB的字符串
        data.add(IntStream.rangeClosed(1, 10_000)
                .mapToObj(__ -> "a")
                .collect(Collectors.joining("")));
    }
}

这边做个小总结

  1. 我们通过MAT可以通过直方图很方便的知道当前堆中哪个对象的数量较多且占据的堆内存较多。同时我们可以通过List objects查看引用链,最终定位到究竟是在哪个类中出现了大量对象导致OOM
  2. 除了直方图外,我们可以使用支配树在更快的时间发现导致OOM的对象
  3. 然后根据线程视图,定位到具体是在哪个地方发生了OOM
  4. 最后呢,我们可以通过OQL查看类,搜索类有几个实例,以及实例在哪几个地方有引用

最后呢,可以到代码地址中下载相关代码,然后本地实践一下。以及本篇文章的内容实际上是学习自极客时间的《Java业务开发常见错误100例》这是一个实战性比较强的专栏,推荐大家也可以去看看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/883899.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript进阶 第二天

深入对象内置构造函数 一. 深入对象 创建对象三种方式构造函数实例成员&静态成员 1.1 创建对象三种方式 ① 利用对象字面量创建对象 const o {name: 哈哈 } ② 利用new Object 创建对象 const o new Object({ name: 哈哈 }) ③ 构造函数创建对象 1.2 构造函数 …

致远OA M1Server RCE漏洞复现

0x01 产品简介 致远M1移动协同软件&#xff0c;结合移动应用特色的信息终端&#xff0c;帮您高效管理&#xff0c;掌控全局&#xff1b;基于移动互联技术的产品&#xff0c;实现全天候在线&#xff0c;随时随地了解企业信息&#xff1b;触控式操作&#xff0c;舒适的滑动体验&a…

Prim+Kruskal(最小生成树)

Prim算法求最小生成树 给定一个 n 个点 m 条边的无向图&#xff0c;图中可能存在重边和自环&#xff0c;边权可能为负数。 求最小生成树的树边权重之和&#xff0c;如果最小生成树不存在则输出 impossible。 给定一张边带权的无向图 G(V,E)&#xff0c;其中 V 表示图中点的集…

FPGA:RS编码仿真过程

FPGA&#xff1a;RS编码仿真过程 RS码是一种纠错性能很强的线性纠错码&#xff0c;能够纠正随机错误和突发错误。RS码是一种多进制BCH码&#xff0c;能够同时纠正多个码元错误。 之前已经记录了在MATLAB中进行rs编解码的过程&#xff0c;现在利用FPGA的IP核实现RS编码的过程&…

Hlang社区-社区导航栏实现

文章目录 前言项目结构导航实现创作中心移动小球消息提示完整代码前言 okey,这里的话是我们社区导航栏的实现: 废话不多说,看看效果: 我甚至为此用New Bing生成了一个Logo。 项目结构 废话不多说,先来看到我们的项目结构: 在这里导航栏是一个组件。 在App.vue里面直…

POST请求负载form-data表单数据需要同步修改header的Content-type

需要读公司的一个接口&#xff0c;显示负载是“表单数据” 一开始没注意类型&#xff0c;按照公司其他接口的情况用了json&#xff0c;结果返回的数据时间阶段没有体现dct的要求&#xff0c;脚本如下&#xff08;错误示范&#xff09; from requests import post from json im…

企业权限管理(十六)-aop日志

SysLog实体类 public class SysLog {private String id;private Date visitTime;private String visitTimeStr;private String username;private String ip;private String url;private Long executionTime;private String method;public String getId() {return id;}public v…

Request+Response

文章目录 1. 介绍2. Request对象2.1 Request继承体系2.2 Request获取请求数据1.获取请求行2.获取请求头3.获取请求体4. 请求参数的通用方式5. 解决中文乱码问题 2.3 Request请求转发请求转发资源间共享数据: 3. Response对象3.0 Response 继承体系3.1 Response设置响应数据的功…

RK3288Android7.1上10寸mipi屏兼容

需求&#xff1a;RK3288Android7.1上10寸mipi屏兼容 思路&#xff1a; 1、修改屏幕厂商提供的屏幕初始化指令为rk可以识别的格式存放在panel-init-sequence 2、修改屏参display-timings 3、确定reset-gpio 实现&#xff1a; 首先根据屏幕厂商提供的初始化指令将panel-init-seq…

AMS的起源,SystemServer的前世今生

作者&#xff1a;OpenGL 前世 SystemServer的创建还得追溯到之前的Zygote进程中的ZygoteInit代码中。这里的Zygote.forkSystemServer()方法就是实现分裂的关键代码。它内部调用了native方法实现了进程分裂。 执行完这行代码之后。我们的系统中就会出现两个一模一样的进程&…

复现基于PYNQ-Z2的手写数字识别卷积加速器设计

来源雪天鱼 基于PYNQ-Z2的手写数字识别卷积加速器设计【持续更新】_雪天鱼的博客-CSDN博客 一、设计思路 1、输入28 x 28 的图片&#xff0c;非png格式&#xff0c;而是txt格式&#xff0c;将图片数据进行量化&#xff0c;存入到txt文件当中。 2、在PL端实现卷积神经网络LeN…

关于Linux Docker springboot jar 日志时间不正确 问题解决

使用Springboot项目的jar&#xff0c;制作了一个Docker镜像&#xff0c;启动该镜像后发现容器和容器中的Springboot 项目的日志时间不正确。 解决 查看容器时间命令为&#xff1a; docker exec 容器id date 1. 容器与宿主机同步时间 在启动镜像时候把操作系统的时间通过&q…

可白嫖的4家免费CDN,并测试其网络加速情况(2023版)

网站加载速度优化过程中&#xff0c;不可避免的会用上CDN来加速资源的请求速度。但是市面上的CDN资源几乎都是要收费的&#xff0c;而且价格还不便宜&#xff0c;对于小公司站长来讲&#xff0c;这将是一笔不小的开销。不过还是有一些良心公司给我们提供了免费的资源&#xff0…

SASS 学习笔记

SASS 学习笔记 总共会写两个练手项目&#xff0c;成品在 https://goldenaarcher.com/scss-study 可以看到&#xff0c;代码在 https://github.com/GoldenaArcher/scss-study。 什么是 SASS SASS 是 CSS 预处理&#xff0c;它提供了变量&#xff08;虽然现在 CSS 也提供了&am…

【C# 基础精讲】异常的类型和处理方法

异常&#xff08;Exception&#xff09;是在程序执行过程中发生的意外或异常情况&#xff0c;例如除零错误、空引用访问、文件不存在等。在C#及其他编程语言中&#xff0c;异常处理是一种重要的机制&#xff0c;用于捕获和处理程序运行时可能出现的错误&#xff0c;以保证程序的…

939. 最小面积矩形;2166. 设计位集;2400. 恰好移动 k 步到达某一位置的方法数目

939. 最小面积矩形 核心思想&#xff1a;枚举矩形的右边那条边的两个点&#xff0c;并用一个哈希表存储相同纵坐标的最近出现的列的列数,不断更新最近出现的左边那条边。 2166. 设计位集 核心思想&#xff1a;这题主要是时间复杂度的优化&#xff0c;用一个flag来标记当前翻转…

word在页眉页脚添加第几页

如果直接在页脚添加数字&#xff0c;整个文档的页脚会是统一的。 这里我们需要的是每一页按照页码排布的文档&#xff0c;所以首先打开页脚设置&#xff1a; 在插入内选择页脚 在弹出的下拉窗口中选择空白即可 在菜单栏会多出“页眉和页脚”的选项卡&#xff0c;选择其中的页…

使用拦截器+Redis实现接口幂等

文章目录 使用拦截器Redis实现接口幂等1.思路分析2.具体实现2.1 创建redis工具类2.2 自定义幂等注解2.2 自定义幂等拦截器2.3 注入拦截器到容器 3.测试 使用拦截器Redis实现接口幂等 1.思路分析 接口幂等有很多种实现方式&#xff0c;拦截器/AOPRedis&#xff0c;拦截器/AOP本…

以对话为场景本质,AIGC 将如何改变游戏规则

8 月 17 日&#xff08;本周四&#xff09;&#xff0c;融云直播课从排查问题到预警风险&#xff0c;社交产品如何更好保障体验、留住用户&#xff1f;欢迎点击报名~ 生成式 AI 公司 MosaicML 以约 13 亿美元的价格被大数据巨头 Databricks 收购&#xff0c;这个发生于 6 月底的…

python获取音乐文件

浏览器打开音乐地址 http://www.htqyy.com/top/hot 点击第一首歌曲&#xff0c;会打开新的网页并且可以获取 改歌曲的id&#xff0c;就是url中的33 在播放页面点击F12&#xff0c;打开开发者调试功能 如下图所示&#xff0c;在script脚本中可以获取歌曲的下载数据 host&#…