JVM 使用mat分析Dump文件排查大对象解决系统full GC问题

摘要:介绍内存分析工具Mat查找大对象的使用方法,定位full GC根源,拉升系统吞吐量,避免内存泄漏。

引言

  线上服务器频繁发生full GC,直接拉低系统吞吐量,甚至OOM。今天我们来一起学习一下如何利用MAT(Memory Analyzer Tool)快速的定位Java程序的大对象和内存泄漏问题。本文实验环境为Mac下安装独立版的Eclipse Memory Analyzer Version 1.13.0。

  对于程序员来说码代码容易,保证代码的稳定性很难。有时候写完一个功能可能只需要一天时间,但是这个功能隐藏的bug导致的线上问题排查可能需要一周或者更长时间。因此,拥有良好的代码结构和编码规范是一个程序员应该长期坚持并为之奋斗的一个目标。但是,百密也难免一疏,没有百分之百没有问题的代码,在产品上线前,我们需要对自己的代码进行充分的自测,解决发现问题,保证产品的稳定性并减少对用户的困扰。今天,就给大家简单介绍一下如何使用MAT(Memory Analyzer Tools)进行Android内存泄漏分析,我只是抛砖引玉,希望大家能够灵活地将这个强大的工具使用起来。

  什么是Dump文件?Dump文件也称为内存转储文件或内存快照文件,是一个进程或者系统在某一个给定时间的内存快照。例如当进程崩溃或进程出现其它问题时,甚至在任何时候,我们都可以使用工具备份系统或进程的内存进行调试和分析。它包含模块信息、线程信息、堆栈调用信息、异常信息等。

查看java服务进程pid

  若想查看指定的进程,如java进程,可以通过管道符和grep命令进行查询,命令如下:

ps -aux | grep java
or
jps -l

JMAP导出dump 文件

  在使用mat进行内存分析前,需要准备好Heap dump文件,避免服务重启后无法复现问题。输出jvm的heap内容到指定文件,live子选项是可选的,假如指定live选项,那么只输出活的对象到文件:

jmap -dump:live,format=b,file=myFullGcDump.bin

  替换为上一节拿到的服务进程号即可,此命令将把dump信息导入到myFullGcDump.bin文件中,供下文分析使用。

  大家在使用上述命令时候一定要小心,如果服务器上的JVM heap过大,会造成应用“Stop the World”。建议使用参数的形式,在启动应用程序的时候就把参数带上,这样会在内存溢出的时候及时的保存线程dump文件。

  把服务器上的myFullGcDump.bin文件下载到本地之后,根据dump文件大小调整mat内存,打开mat初始化文件MemoryAnalyzer.ini,将启动内存调整至比dump大的适当大小,例如-Xmx8120m:

  现在开启我们的dump文件分析之路吧。

梳理mat功能

  先通过一张图来了解强大的Eclipse 插件mat的功能菜单:

  第一个菜单i用于概览全局信息,这是最常用的一个菜单。

mat分析大对象

  下面进入本文核心,分析前面下载的dump文件。通过下图中的Open a Heap Dump按钮加载需要分析的本地dump文件,历史浏览版文件会在按钮下方展示。

Getting Started

  在Getting Started Wizard页面勾选Leak Suspects Report,然后,单击Finish输出首份报告:

  这时可以大概看出内存中存在的问题,此处可以初步定位到内存异常的信息,但是具体问题定位还需继续进行分析。overview界面会以饼图的方式显示当前消耗内存最多的几类对象,可以使我们对当前内存消耗有一个直观的印象。但是,除非你的程序内存泄漏特别明显或者你正好在生成hprof文件之前复现了程序的内存泄漏场景,你才可能通过这个界面猜到程序出问题的地方。

Dominator tree

  开始 Dump 分析时,首先应使用 Dominator tree 了解各支配树起点对象所支配内存的大小,进而了解哪几个root对象是 GC 无法释放大内存的原因。

  Shallow Heap 和 Retained Heap分别表示对象自身不包含引用的大小和对象自身并包含引用的大小。默认的大小单位是 Bytes,可以在 Window - Preferences 菜单中设置单位,图中设置的是KB。

  针对非数组类型的对象,Shallow Heap的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包括一些java语言特性的数据存储单元。针对数组类型的对象,Shallow Heap的大小是数组元素对象的大小总和。

  当个别对象支配树的 Retained Heap 很大且存在明显倾斜时,可以重点分析它们的对象支配关系,展开RingBuffer(老铁的代码可能是其他类)的子树进一步定位到问题根因,如下图可看出最终是业务类的 LogData 对象持有的 String字符串过大:

  把鼠标悬浮在截图中选中行上方时,会显示value的具体值,这是 full GC的根源。其实,刚看到RingBuffer时,我们并不确认它是被哪个业务对象引用的,故需要展开子树,查看具体是哪个业务对象。

Histogram

  功能介绍:以直方图的方式来显示当前内存使用情况可能更加适合较为复杂的内存泄漏分析,它默认直接罗列每个类实例的数量和累计内存占比,包括自身内存占用量(Shallow Heap)及支配对象的内存占用量(Retained Heap)。

  它支持按对象数量、Retained Heap、Shallow Heap(默认排序)等指标排序;支持按正则过滤;支持按 package、class loader、super class、class 聚类统计。使用入口:MAT 主界面 → Histogram

  在这里,我们同样可以发现 LogData 对象吃掉了很大内存,当然,还有另外两个对象:

  根据Objects进行排序后可见,业务类LogData存在131072个对象,消耗的内存也比较靠前,SdkSpan和SpanData存在同样的问题,因此这三个类必定存在问题。

使用场景:有些情况下, Dominator tree 无法展现出热点对象(如按 class 聚合也无明显热点对象,此时 Dominator tree 很难做关联分析判断哪类对象占比高),这时可以使用 Histogram 查看所有对象所属类的分布,快速定位占据 Retained Heap 大头的类。

使用技巧:Integer,String 和 Object[] 一般不直接导致内存问题。为更好的组织视图,可以通过 class loader 或 package 分组进一步排查问题。

Leak Suspects

功能介绍:具备自动检测内存泄漏功能,罗列可能存在内存泄漏的问题点。

使用入口:一般当存在明显的内存泄漏时,分析完Dump文件后就会展现,也可以如下图在 MAT 主页找到 Leak Suspects。

使用场景:需要查看引用链条上占用内存较多的可疑对象。这个功能可解决一些基础问题,但复杂的问题往往帮助有限。

举例分析

  下图中 Leak Suspects 视图展现了两个线程支配了绝大部分内存。

  下图是点击上图中 Keywords 中 "Details" ,获取实例到 GC Root 的最短路径、dominator 路径的细信息。

  同样可以发现使用了RingBuffer的业务类创建了很多线程。

小结

  至此本文基于线上full GC 实战案例讲解了 MAT 工具中常用功能的使用方法和适用场景,尤其是各种功能的组合使用,对快速把脉 JVM 内存问题大有裨益。当然,这里介绍的Mat工具使用方法只是冰山一角,有兴趣的老铁们可以继续深挖。

  那么大家对于这件事都是怎么看的呢?欢迎在文章下方留言讨论,三人行必有我师焉!小编会仔仔细细地看每条评论。

Reference

posted @ 2022-12-17 19:51  楼兰胡杨  阅读(10207)  评论(0编辑  收藏  举报