使用MAT分析OOM问题

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 {@AutowiredFooService fooService;public static void main(String[] args) {SpringApplication.run(OOMApplication.class, args);}@Overridepublic 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.rhkb.cn/news/91994.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Monge矩阵

Monge矩阵 对一个m*n的实数矩阵A&#xff0c;如果对所有i&#xff0c;j&#xff0c;k和l&#xff0c;1≤ i<k ≤ m和1≤ j<l ≤ n&#xff0c;有 A[i,j]A[k,l] ≤ A[i,l]A[k,j] 那么&#xff0c;此矩阵A为Monge矩阵。 换句话说&#xff0c;每当我们从矩阵中挑…

jQuery EasyUI datagrid 无记录时,增加“暂无数据“提示

1、在onLoadSuccess中添加如下代码&#xff1a; if (data.total 0) {var body $(this).data().datagrid.dc.body2;body.find(table tbody).append(<tr><td width" body.width() " style"height: 35px; text-align: center;"><h5>暂…

C++的IO流

目录 C语言的输入与输出 流是什么 CIO流 C标准IO流 C文件IO流 stringstream的简单介绍 在C语言中&#xff0c;如果想要将一个整形变量的数据转化为字符串格式&#xff0c;如何去做&#xff1f; 将数值类型数据格式化为字符串 字符串拼接 序列化和反序列化结构数据 注…

管理类联考——逻辑——综合推理——汇总篇——要点

一、真话假话题 #mermaid-svg-gmlWWCoVLQr21gdi {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-gmlWWCoVLQr21gdi .error-icon{fill:#552222;}#mermaid-svg-gmlWWCoVLQr21gdi .error-text{fill:#552222;stroke:#552…

00 - 环境配置

查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;GIT常用场景- 目录 文章目录 1. 环境说明2. 安装配置2.1 配置user信息2.2 config的三个作用域 3. 建git仓库3.1 把已有的项目代码纳入git管理3.2 新建的项目直接用git管理3.3 配置local的user和email3.4 优先级&…

图像像素梯度

梯度 在高数中&#xff0c;梯度是一个向量&#xff0c;是有方向有大小。假设一二元函数f(x,y)&#xff0c;在某点的梯度有&#xff1a; 结果为&#xff1a; 即方向导数。梯度的方向是函数变化最快的方向&#xff0c;沿着梯度的方向容易找到最大值。 图像梯度 在一幅模糊图…

《电路》基础知识入门学习笔记

文章目录&#xff1a; 一&#xff1a;电路模型和电路规律 1.电路概述 2.电路模型 3.基本电路物理量&#xff1a;电流、电压、电功率和能量 4.电流和电压的参考方向 5.电路元件—电阻 6. 电路元件—电压源和电流源 7.受控电源 8.基尔霍夫&#xff08;后面都要用这个方法…

“之江数据安全治理论坛”暨《浙江省汽车数据处理活动规定(专家建议稿)》研讨会顺利召开

研讨会主题 8月10日&#xff0c;“之江数据安全治理论坛”暨《浙江省汽车数据处理活动规定&#xff08;专家建议稿&#xff09;》研讨会在浙江大学计算机创新技术研究院举办。 本次研讨会的主题聚焦于“智能网联汽车的数据安全与数据合规”&#xff0c;邀请行业主管部门和数据…

使用 Docker 部署 canal 服务实现MySQL和ES实时同步

文章目录 0. 环境介绍0. 前置步骤1. 安装Kibana和Elasticsearch2. 安装Canal和Canal Adapter2.1 修改数据库配置2.1.1 修改配置2.1.2 验证mysql binlog配置2.1.3 查看日志文件2.1.4 用JDBC代码插入数据库 2.2 安装Canal Server2.3 安装Canal Adapter修改两处配置文件配置文件取…

浏览器 - 事件循环机制详解

目录 1&#xff0c;浏览器进程模型进程线程浏览器的进程和线程1&#xff0c;浏览器进程2&#xff0c;网络进程3&#xff0c;渲染进程 2&#xff0c;渲染主线程事件循环异步同步 JS 为什么会阻塞渲染任务优先级 3&#xff0c;常见面试题1&#xff0c;如何理解 js 的异步2&#x…

如何使用CSS实现一个响应式网格布局?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用CSS实现响应式网格布局⭐ 设置基本的HTML结构⭐ 创建基本的CSS样式⭐ 添加媒体查询以实现响应式效果⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端…

【wiki】电竞助手掉落提醒 EsportsHelper「Webhook」「钉钉」「饭碗警告」「企业微信」「Discord」

介绍 本项目链接 Github电竞助手链接 github上项目电竞助手(EsportsHelper)的掉落提醒配置教程,当有掉宝的时候会发送你信息提示. 至于这个脚本是怎么使用的简单说一下,就是通过自动观看英雄联盟直播 从而获取奖励(仅限直营服),有兴趣的可以去github上看readme,非常详细,支持…

ARM--day2(cpsr、spsr、数据搬移指令、移位操作指令、位运算操作指令、算数运算指令、比较指令、跳转指令)

.text .global _gcd _gcd:mov r0,#9mov r1,#15b loop loop:cmp r0,r1beq stopsubhi r0,r1bhi loopsubcc r1,r0bcc loopstop:b stop.end用for循环实现1~100之间和5050 .text .global _gcd _gcd:mov r0,#0x0mov r1,#0x1mov r2,#0x64b loop loop:cmp r1,r2bhi stopadd r0,r0,r1ad…

epoll数据结构

目录 1.大量的fd 集合。选择什么数据结构&#xff1f;2、Epoll 数据结构Epitem 的定义Eventpoll 的定义 1.大量的fd 集合。选择什么数据结构&#xff1f; 查找频率很高的数据结构 1.红黑树 2.哈希&#xff08;扩容缩容&#xff09; 3. b/btree &#xff08;降低树的高度&#…

【学会动态规划】环形子数组的最大和(20)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

UI设计师个人工作总结范文精选

UI设计师个人工作总结范文(一) 在忙忙碌碌中&#xff0c;2019年又将过去了&#xff0c;在这一年当中&#xff0c;设计部无论是在运作模式、设计产值、还是人员结构&#xff0c;各方面的变化都比较大。 设计部的运作模式是从7月底开始进行调整的&#xff0c;以独立承包制的运营方…

pgsql checkpoint机制(1)

检查点触发时机 检查点间隔时间由checkpoint_timeout设置pg_xlog中wall段文件总大小超过参数max_WAL_size的值postgresql服务器在smart或fast模式下关闭手动checkpoint 为什么需要检查点&#xff1f; 定期保持修改过的数据块作为实例恢复时起始位置&#xff08;问题&#xf…

网盘与相册服务PDS

引言&#xff1a;作为一名开发者&#xff0c;我将通过对PDS&#xff08;Personal/Enterprise Drive System&#xff09;的体验使用&#xff0c;分享一下本人对以下方面的使用体验。 1. 开发个人/企业网盘 功能与应用 PDS作为一种网盘服务中间件产品&#xff0c;为开发者提供了…

【调整奇数偶数顺序】

调整奇数偶数顺序 1.题目 输入一个整数数组&#xff0c;实现一个函数&#xff0c; 来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分&#xff0c; 所有偶数位于数组的后半部分。 2.题目分析 这道题首先用到的方法是冒泡排序的思想&#xff0c;首先通过冒泡排序…

C#__匿名方法和Lambda表达式

class Program{static void Main(string[] args){// 匿名方法&#xff1a;方法没有名字Func<int, int, int> plus delegate (int a, int b){return a b;};// 这里相当于直接把要引用的方法直接写在后面// 优点&#xff1a;减少了要编写的代码&#xff0c;减少代码的复杂…