堆内存OOM分析

1. 背景

7月6号系统应用频繁挂掉,监控显示内存、CPU、GC次数短时间内飙升,日志java. lang. OutOfMemoryError: Java heap space显示JVM发生堆内存的OOM,并成功dump了内存快照。

在这里插入图片描述

2. OOM的基本分析思路

2.1 OOM的易发区域

在《Java虚拟机规范里》中除程序计数器外,其它运行时内存区域都有可能发生OOM。具体到HotSopt,栈发生OOM的机率很小(不支持栈的动态扩展,除非在线程申请栈时就内存不足)。常见发生OOM的区域主要有两种:

Java堆内存

当堆内存没有足够的空间存放新对象时会抛出内存溢出错误java.lang.OutOfMemoryError: Java heap space

直接内存

使用NIO可以直接在堆外分配内存,其默认大小与堆内存最大值相同,且受总物理内存限制。

当将绝大数数内存分配给堆,留给直接内存的大小就会变小,发生OOM的几率变高。错误日志:java.lang.OutOfMemoryError: Direct buffer memory或者at sun.misc.Unsafe.allocateMemory(Native Method)

对于方法区,在Java 8中方法区由基于本地内存的元空间实现,默认情况下其大小仅受物理内存限制,发生内存溢出的可能性很小。

2.2 获取dump文件

进一步分析OOM原因时,应该获取到发生OOM时刻时的Dump文件,一般有两种方式:

jmap

jmap命令用于生成堆转储快照(dump文件),除此之外还可以查询finalize执行队列和方法区等详细信息。

jmap命令格式:jmap [option] vmid

option的选项有:

  • -dump:生成dump文件,添加:live参数时JVM会在dump前首先执行一次Full GC,以仅dump当前存活的对象;
  • -heap:获取堆的详细信息,譬如所使用的GC、参数配置、分代信息等;
  • -histo:获取对象的统计信息;
  • -F:当-dump无效时,可以使用-F强制生成dump文件。

-XX:+HeapDumpOnOutOfMemoryError

通过参数- XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现堆内存溢出时Dump出当前的内存堆转储快照以便进行事后分析。

2.3 OOM的常见原因分析

堆内存的内存泄漏与内存溢出

发生堆内存OOM时,首先应该确认导致OOM的对象是否有必要,即是否存在内存泄漏情况。

如果确认是发生了内存泄漏,使用MAT工具分析泄漏对象的引用路径、与哪些GC Roots相关联才导致垃圾收集器无法回收。定位到对象的创建位置,进而分析出导致内存泄漏的具体代码位置。

如果对象是必须存活的,则应检查代码上是否存在对象的生命周期过长,JVM的内存空间是否还有向上调整的空间。

堆外内存溢出

发生直接内存溢出时,Dump文件不会有什么明显的异常,在加入-XX:+HeapDumpOnOutOfMemoryError后也没有Dump文件产生。若程序中使用了NIO等技术,则应该重点关注直接内存方面的原因。

虚拟机不会直接内存不足时进行垃圾回收,只能在发生内存溢出时使用System.gc()触发垃圾收集,或者在Full GC时顺便收集直接内存。

MAT的使用

MAT分析的三步曲:

  1. 首先对发生时刻的系统内存状况有一个整体印象;
  2. 找出最有可能出现内存泄漏等问题的元凶,通常是耗用内存最多的对象;
  3. 进一步分析内存消耗大户的具体情况,看看有什么异常行为。

2.1 基本概念

2.1.1 支配树

MAT提供了一种被称为支配树(Dominator Tree)的对象图。支配树体现了对象实例之间的支配关系。

支配者

如果在对象引用图中,如果所有到对象B的引用路径都经过A,则对象A支配着对象B。

直接支配者

对象最近的支配者被称为直接支配者。

支配树

根据对象引用图构建的支配树,在支配树中,每个对象都是其子节点的直接支配者,可以轻松的识别对象直接的依赖关系。

该图显示了一组对象A到H,它们排列在左侧的对象图结构中。 在右侧,该图显示了与支配者树相同的对象组。

保留集(Retained Set)

支配图中对象A的子树,即所有被对象A支配的对象集合,被称为对象A的保留集。

2.1.2 Shallow Heap 与 Retained Heap

Shallow Heap表示对象本身占用的内存,而不包括其引用对象所占用的空间。比如,一个基本类型int占用4个字节,一个引用类型占用8个字节。

Retained Haep表示该对象及其保留集对象所占用的内存空间,即该对象和被其支配对象的内存总和。

当一个对象不再被引用时,Retained Heap表示了一共可以释放的内存大小。

2.2 视图、报告、功能

Overview视图

概述界面,可以对Heap Dump有一个大致的了解,并提供了一些视图、报告的入口。

Histogram视图

以类维度统计每个类的实例个数,Shallow Heap占用的内存和Retained Heap的估计占用内存,以及对象的详细信息。

Dominator Tree视图

以支配树的形式展示对象所占用的空间以及详细信息。

Thread Overview视图

工具栏的if available, show each thread name…,以线程维度统计内存占用情况,展示线程的堆栈以及局部变量等详细信息。

Top Consumers视图

对占用内存超过堆内存1%的对象作多个维度的分析。从类、包等多个维度,分析内存占用及支配树情况。

Leak Suspects Report

内存泄漏报告,给出可能存在内存泄漏位置的分析报告,包括线程堆栈、以线程对象为根的支配树等信息。

Group By功能

工具栏的Group result by…可以按照类、包、class loader等维度对结果进行分组统计。

Query Broswer 功能

List objects查看某个对象的引用关系(with outgoing references 或 with incoming references)。

Paths to GC Roots查看对象到GC Roots的引用。

3. 本文案例分析

Overview界面显示2GB的堆内存,Tomcat的一个线程对象的Retained内存占用达到了1.5GB,怀疑问题来源于用户的前端页面请求(服务间调用采用RPC)。

在这里插入图片描述

查看线程视图,发现该线程中存在大量的com.mysql.jdbc.ByteArrayRow对象,数据库返回了太多的行。

在这里插入图片描述

继续追踪该堆栈,查找该查询的SQL语句,定位到了一个没有条件的全表查询语句。

在这里插入图片描述

最后,结合业务代码,由于后端没有对查询条件做充分的校验,导致了对一个千万级表执行了全表查询,应用读取了太多的行,最终导致了OOM。

参考

The dominator tree view—IBM Knowledge Center

MAT从入门到精通

使用Eclipse Memory Analyzer Tool(MAT)分析线上故障

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值