2019-09-19 2019-09-20这两天小程序后台服务多次出现假死情况,服务没挂但是访问接口无响应。
运维的监控脚本发现某服务器上的应用无法访问发送邮件给项目成员。
查看应用日志只发现了一条连接的错误信息如下:
内容过于单薄,并不能通过该条日志定位到问题。
使用jmap -heap [pid] 查看当前应用的堆内存使用情况如下:
重点关注Heap Usage,可见新生代和老年代的空间满了,这里可以确定问题是OutOfMemery(OOM)。
现在的问题转移到定位产生内存溢出的根源,这里要先获取heap dump信息。
这里主要介绍两种:
jmap -dump:live,format=b,file=heap.hprof
-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文件后,将文件下载到本地备用。
堆内存文件分析工具有几种,如jdk自带的Viusal VM,MAT,Jhat等。本次使用MAT来分析dump文件。MAT的内容参考[Memmeory Analyzer Tool(MAT)简介]
下载地址:https://www.eclipse.org/mat/downloads.php
选择合适版本下载以后,解压到指定目录,右键执行MemoryAnalyzer.exe即可
运行MAT,打开下载下来的dump文件(hprof类型)。如果发现文件加载缓慢或者有卡顿可以修改MemoryAnalyzer.ini配置,加大启动内存。其默认内存是512m,如果文件偏大建议加大启动内存。mat分析dump文件时比较耗内存,内存较小分析的速度就会偏慢甚至卡顿。
默认选择Leak Suspects Report,自动分析生成内存泄漏疑点报告
打开结果
查看leak suspects报告
从报告上来看有1353759个ByteArrayRow对象。找一个ByteArrayRow对象查看详情
很明显这是一个数据库记录,点击 list object ->with outgoing reference 查询该对象引用了哪些对象。
引用的对象有五个,应该这是一条数据库记录,所以直接看DruidPooledConnection对象
复制SQL语句
注意到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
选择raw 右键go into
选择parameterValues 右键Go Into 发现参数值为’’,空字符串
至此,问题已经定位到了,根据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) …
问题根源定位到了,那么解决方案自然而然就产生了。查询的时候,参数控制更加严格,即不允许空字符串。
MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。可以用于查找内存泄露以及查看内存消耗情况。MAT是基于Eclipse开发的,是一款免费的性能分析工具。
获取堆转储的概览信息:顶部是对象的大小和总数,然后是根据retained size排序的对象饼图。在导入堆转储的时候会自动打开Overview界面,或者点击工具栏上的I图标打开。
直方图可通过工具栏上的直方图图标打开,或者在Overview页面Action标签下的Histogram。
直方图列出了按其类别分组的对象。内存分析器可以非常快速地估计保留的大小。这是继续进行分析的一个很好的指标。
Shallow Heao和Retained Heap参考Shallow Heap 和 Retained Heap默认的大小单位是 Bytes,可以在 Window - Preferences 菜单中设置单位。
通过直方图视图可以很容易找到占用内存最多的几个类(通过Retained Heap排序),还可以通过其他方式进行分组(见下图)。
可在工具栏点击Dominator Tree图标打开,或者Overview Action标签下的Dominator tree打开视图。
在此视图中列出了每个对象(Object Instance)与其引用关系的树状结构,同时包含了占用内存的大小和百分比。
通过Dominator tree视图可以很轻易得找到占用内存最大的几个对象(根据Retained Heap或Percentage排序),和Histogram类似,可以通过不同的方式进行分组显示。
打开方式:导入dump文件时,默认选择Leak Suspects Report或者在Overview页面下的Report标签中点击Leak Suspects
包括泄漏疑点和系统概述,如下图: