java oom 导致程序假死_记一次OOM导致服务器假死的排查过程

本文记录了一次由于Java OOM导致服务器假死的排查过程。通过Jmap获取Heap Dump,使用Memory Analyzer Tool (MAT)进行分析,最终发现因为空字符串查询全表数据导致大量对象创建,触发内存溢出。解决方案是加强对查询参数的控制,避免空字符串引起全表扫描。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记一次OOM导致服务器假死的排查过程

背景及现象

问题发现

获取Heap Dump

1、使用Jmap命令导出

2、使用 JVM 参数获取 dump 文件

分析Heap Dump文件

MAT 安装

使用MAT分析

解决方案

Memmeory Analyzer Tool(MAT)简介

MAT介绍

基础概念

Overview(概览)

Histogram(直方图)

Dominator Tree(支配树)

Leak Suspects Report(泄漏疑点报告)

背景及现象

2019-09-19 2019-09-20这两天小程序后台服务多次出现假死情况,服务没挂但是访问接口无响应。

问题发现

运维的监控脚本发现某服务器上的应用无法访问发送邮件给项目成员。

查看应用日志只发现了一条连接的错误信息如下:

3c38ae984ae00501d9ef6faa2cc45cc3.png

内容过于单薄,并不能通过该条日志定位到问题。

使用jmap -heap [pid] 查看当前应用的堆内存使用情况如下:

5fbd868aac094a3e3fc7735147b6675b.png

重点关注Heap Usage,可见新生代和老年代的空间满了,这里可以确定问题是OutOfMemery(OOM)。

现在的问题转移到定位产生内存溢出的根源,这里要先获取heap dump信息。

获取Heap Dump

这里主要介绍两种:

1、使用Jmap命令导出

jmap -dump:live,format=b,file=heap.hprof

2、使用 JVM 参数获取 dump 文件

-XX:+HeapDumpOnOutOfMemoryError

这个参数非常有用,需要我们分析内存的场景一般都是出现了OOM的情况。

-XX:+HeapDumpBeforeFullGC

当 JVM 执行 FullGC 前执行 dump。

-XX:+HeapDumpAfterFullGC

当 JVM 执行 FullGC 后执行 dump。

以上参数设置以后堆转储将默认在JVM的“当前目录”中生成,可结合如下命令显示重定向 dump 文件存储路径。

-XX:HeapDumpPath=test.hprof

注意:JVM 生成 Heap Dump 的时候,虚拟机是暂停一切服务的。如果是线上系统执行 Heap Dump 时需要注意。

得到dump文件后,将文件下载到本地备用。

分析Heap Dump文件

堆内存文件分析工具有几种,如jdk自带的Viusal VM,MAT,Jhat等。本次使用MAT来分析dump文件。MAT的内容参考[Memmeory Analyzer Tool(MAT)简介]

MAT 安装

下载地址:https://www.eclipse.org/mat/downloads.php

选择合适版本下载以后,解压到指定目录,右键执行MemoryAnalyzer.exe即可

a9040ebabef897d8f20cdb824d53ce15.png

使用MAT分析

运行MAT,打开下载下来的dump文件(hprof类型)。如果发现文件加载缓慢或者有卡顿可以修改MemoryAnalyzer.ini配置,加大启动内存。其默认内存是512m,如果文件偏大建议加大启动内存。mat分析dump文件时比较耗内存,内存较小分析的速度就会偏慢甚至卡顿。

7dc5c677a54d060765dc0ffc890be8af.png

默认选择Leak Suspects Report,自动分析生成内存泄漏疑点报告

eaab4327514f917fe41837c6e1ba0b9e.png

打开结果

c03b102eaf94edcfd601740b2b4fbbdf.png

查看leak suspects报告

94be50432eaee2ecffcc4a3ae6c6afe1.png

0d56071ca6035562692e999a6c8a3fc8.png

从报告上来看有1353759个ByteArrayRow对象。找一个ByteArrayRow对象查看详情

ecd85881191e18ce96fe8ed0f68ebb1f.png

很明显这是一个数据库记录,点击 list object ->with outgoing reference 查询该对象引用了哪些对象。

8cc239bd515f7f9f80701bed5e47c36f.png

283dd49095bc00858363190b4f59a43e.png

引用的对象有五个,应该这是一条数据库记录,所以直接看DruidPooledConnection对象

c57165bc2ba2b511ccba02e4890ae817.png

复制SQL语句

a13ab0c667373a4c93bd5743463ad99a.png

注意到Sql语句为:

SELECT id, full_name, nick_name, union_id, avatar_url, gender, age, occupation, QQ, WW, month_income, MSN, card, photo_id,

country, province, city, area, area_id, town, phone, telephone, mobilephone, email, birthday,

register_time, channel, lastLoginDate, availableBalance, freezeBlance, gold, member_level_id, empirical_value,

current_add_empirical_value, current_add_empirical_value_date, account_status, pay_time,

pay_valid_time, update_time, register_mobile, is_delete

FROM

yh_full_member_0

WHERE

union_id=?

AND

is_delete=0

另外四个conn对象的sql语句与上述相同,唯一的差异就是表名为yh_full_member_1,yh_full_member_2,yh_full_member_3,yh_full_member_4。该SQL语句只传了一个参数union_id去查看参数值。选择stmt右键go into

9f192ecde1528a3959693dbd7164856e.png

选择raw 右键go into

5605d88d8c6bb00e8fd2af83fca7694d.png

选择parameterValues 右键Go Into 发现参数值为’’,空字符串

241a2c0168bf18c0c274fec3b8cc83e9.png

d019aa16e780da1df0dad5ad7e2ce2d9.png

至此,问题已经定位到了,根据union_id查询数据时,union_id传值为空字符串,导致该条查询语句查询全表数据而创建了100多W个对象导致内存溢出,。有五个conn的原因是该项目的会员表使用Sharding sphere进行分表,yh_full_member共有五张表,平均每张表的记录大概是400多W。查询的语句为 select … from yh_full_member where … 经过Sharding根据路由规则实际改写成了五条sql语句,select … from yh_full_member_(0-4) …

解决方案

问题根源定位到了,那么解决方案自然而然就产生了。查询的时候,参数控制更加严格,即不允许空字符串。

Memmeory Analyzer Tool(MAT)简介

MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。可以用于查找内存泄露以及查看内存消耗情况。MAT是基于Eclipse开发的,是一款免费的性能分析工具。

MAT介绍

基础概念

Shallow Heap 和 Retained Heap

1) Shallow Heap表示对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。

2)Retained Heap是该对象自己的Shallow Heap,并加上从该对象能直接或间接访问到对象的Shallow Heap之和。换句话说,Retained Heap是该对象GC之后所能回收到内存的总和。

Retained Heap不包含除自身之外GC Root能直接访问的对象。比如GC Root 能访问A、C对象,A能直接或间接访问C对象,那么C对象是不计入在A对象的Retained Heap里的。

对象引用(Reference)

对象引用按从最强到最弱有如下级别,不同的引用(可到达性)级别反映了对象的生命周期:

强引用(Strong Ref):通常我们编写的代码都是强引用,于此相对应的是强可达性,只有去掉强可达性,对象才能被回收。

软引用(Soft Ref):对应软可达性,只要有足够的内存就一直保持对象,直到发现内存不足且没有强引用的时候才回收对象。

弱引用(Weak Ref):比软引用更弱,当发现不存在强引用的时候会立即回收此类型的对象,而不需要等到内存不足。通过java.lang.ref.WeakReference和java.util.WeakHashMap类实现。

虚引用(Phantom Ref):根本不会在内存中保持该类型的对象,只能使用虚引用本身,一般用于在进入finalize()方法后进行特殊的清理过程,通过java.lang.ref.PhantomReference实现。

GC Roots和Reference Chain

JVM在进行GC的时候是通过使用可达性来判断对象是否存活,通过GC Roots(GC根节点)的对象作为起始点,从这些节点开始进行向下搜索,搜索所走过的路径成为Reference Chain(引用链),当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

Overview(概览)

获取堆转储的概览信息:顶部是对象的大小和总数,然后是根据retained size排序的对象饼图。在导入堆转储的时候会自动打开Overview界面,或者点击工具栏上的I图标打开。

918fe748f450b8d7f9bbda3841ebc73c.png

Histogram(直方图)

直方图可通过工具栏上的直方图图标打开,或者在Overview页面Action标签下的Histogram。

直方图列出了按其类别分组的对象。内存分析器可以非常快速地估计保留的大小。这是继续进行分析的一个很好的指标。

Shallow Heao和Retained Heap参考Shallow Heap 和 Retained Heap默认的大小单位是 Bytes,可以在 Window - Preferences 菜单中设置单位。

b9074fa496ef6eeba1996c0502f06916.png

通过直方图视图可以很容易找到占用内存最多的几个类(通过Retained Heap排序),还可以通过其他方式进行分组(见下图)。f2e4b59ccd33d907c4099f98ee1ff316.png

Dominator Tree(支配树)

可在工具栏点击Dominator Tree图标打开,或者Overview Action标签下的Dominator tree打开视图。

在此视图中列出了每个对象(Object Instance)与其引用关系的树状结构,同时包含了占用内存的大小和百分比。

通过Dominator tree视图可以很轻易得找到占用内存最大的几个对象(根据Retained Heap或Percentage排序),和Histogram类似,可以通过不同的方式进行分组显示。

1c784833575c272e1cecc87bb20092d6.png

Leak Suspects Report(泄漏疑点报告)

打开方式:导入dump文件时,默认选择Leak Suspects Report或者在Overview页面下的Report标签中点击Leak Suspects

包括泄漏疑点和系统概述,如下图:

25311f94290db8c9c159619a4713367a.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值