又一次线上OOM排查经过

最近线上一个服务又出现了频繁Full GC的情况,导致提供的业务经常超时。问题出现非常不稳定,经过两周的时候,终于又捕捉到了一次Full GC,于是联系运维做Heap Dump之后,经过一系列分析,终于解决问题。这次的问题稍微复杂一点,但是也比较有代表性,用到了VisualVM和MAT两个工具,继续记录如下。

现象

这次使用公司的CAT监控平台看到的内存表现如下:

可以看到,具体表现是:

  1. 在很长一段时间内(数个小时),New GC比较频繁,Full GC较少(一小时个位数)。
  2. 过了某一时间点后,Full GC增加,New GC则减少。
  3. 将服务切换下线后,Memory Free逐渐回升,Full GC减少。

然后观察某一时刻的JMAP histo的内容如下:

 num     #instances         #bytes  class name
----------------------------------------------
1: 5958517 5840833584 [C
2: 37983 706246056 [B
3: 5960539 190737248 java.lang.String
4: 1522282 126603936 [Ljava.lang.Object;
5: 3692000 88608000 java.lang.Double

可以看到"[C"即"char[]"占用内存达到了5.8G,它正是String的内部结构,换句话说,因为存在了大量的大String导致这个问题。

联系运维进行了Heap Dump之后,就开始了分析的过程。因为服务器内存有8G,所以相应的DUMP也有8G,对分析也造成了一点小困难。

下面是一些工具的使用过程,操作系统是OS X 10.8。

使用VisualVM分析

首先使用VisualVM对Heap Dump进行分析。分析需要比较大的内存,而VisualVM默认的内存是256M,所以需要先设置/Applications/VisualVM.app/Contents/Resources/visualvm/etc/visualvm.conf中的最大内存量,这里我们设置成了4G-J-Xmx4096m

好了,现在打开dump文件,整个分析过程共占用内存2G,仍然在可接受范围。之后分析内存,可以看到跟之前histo一样的类关系。

不同的是,这时候可以点进去,查看具体的对象。这里看到,竟然有几个大的String占用了756M的内存,看来我们找到问题了。

发现一个奇怪的现象:“计算保留大小”之后,这些String的保留大小都是0。

保留大小是什么呢?

它是分析工具从GC roots开始查找,找到的所有不会回收的对象,然后按照引用关系,计算出这个“对象以及它引用的对象”的内存大小。这里很有趣的是,这些对象的保留大小都是0。

开始我甚至以为是工具出了问题,后来想想,其实答案很简单:这些大String是临时对象,没有什么对象持有它——通过分析这些String的依赖关系也说明了这一点。这些对象是可以被回收的,换句话说,并不是有明显的内存泄露。

这个“没有对象持有的String”很有意思,让我想到了,会不会是log的过程中,产生了这个String?因为log的时候,会调用对象的toString()方法,而一个大对象的toString()可能是很大的。查看了一下String的内容,是[Class[field1=xxx,field2=yyy]]这种结构,几乎可以肯定是toString()的结果,那么我们现在只要查看一下具体的内容,到底是哪里太大就可以了。

可惜的是,VisualVM里我尝试了各种方法,都没有办法Dump出这个对象的数据,甚至还尝试了VisualVM提供的OQL。

OQL是一种类SQL的表达式,同时整合了一些Javascript的函数功能,但是它的缺点就是太慢了…最后尝试用OQL Dump对象未果。

使用MAT分析

转而尝试一下MAT。Eclipse MAT(Memory Analyzer Tool)是一个强大的内存分析工具,它可以方便的分析内存泄露的问题。实际使用之后,确实也感觉到比VisualVM更加强大一些。

我们使用MAT打开Dump文件,这也是一个漫长的过程。与VisualVM不同的是,MAT分析的时候,会在本地产生几个临时文件保存分析结果,所以相应的内存占用会小一些(1G左右),第二次打开也会快很多。

打开完成后,我们看到可以看到几个占用内存较大的对象,这就是最可疑的内存泄漏源。意外的是Size显示的只有1.3GB,而且我们的大String都没在里面。

开始我们一度认为是Dump文件有错,后来上网搜索才知道,因为MAT的主要目标是排查内存占用量,所以默认大小是不计算不可达对象的——而我们的String都是不可达对象。对应Eclipse的文档里有介绍http://wiki.eclipse.org/MemoryAnalyzer/FAQ#How_to_analyse_unreachable_objects

于是我们按照说明,在"Preferences=>Memory Analyzer"中勾选"Keep Unreachable Objects",删除索引文件Dump同路径下的所有".index",即可看到所有的对象。

点击Histogram,即可看到类的占用。在类上选择"List Objects",即可看到所有对象。在对象上选择"Copy=>Value to File",即可保存到文件。

原理

之后我们通过分析对象,发现是因为某个用于缓存的对象使用了一个List结构,但是添加元素的没有去重,导致List越来越大造成的。这个对象本身占用内存只有110M,但是toString()出来的字符串达到700M的大小,所以引起了频繁的GC——最开始对象小的时候是New GC,后来对象大了,直接进入了年老代,就变成了Full GC。

总结

回到之前的问题,通过这次分析,我们可以这么总结:

  1. 将服务切换下线后,Memory Free逐渐回升,Full GC减少,Heap Dump的可达对象较少

    这种情况不是内存泄露,有可能是对象创建开销太大造成的。

  2. 在1的前提下,New GC很频繁

    这种情况可能是频繁创建小对象,或者创建一些较大的对象(尚不足以直接进入年老代)

  3. 在1的前提下,Full GC很频繁

    这种情况是频繁创建大对象,或者创建了一些超大对象导致的。

  4. 第3种情况下,大对象toString()产生的大String很可能是罪魁祸首

【转】又一次线上 OOM 排查经过的更多相关文章

  1. 一次线上OOM故障排查经过

    转贴:http://my.oschina.net/flashsword/blog/205266 本文是一次线上OOM故障排查的经过,内容比较基础但是真实,主要是记录一下,没有OOM排查经验的同学也可以 ...

  2. Java线上问题排查思路及Linux常用问题分析命令学习

    前言 之前线上有过一两次OOM的问题,但是每次定位问题都有点手足无措的感觉,刚好利用星期天,以测试环境为模版来学习一下Linux常用的几个排查问题的命令. 也可以帮助自己在以后的工作中快速的排查线上问 ...

  3. java:线上问题排查常用手段(转)

    出处:java:线上问题排查常用手段 一.jmap找出占用内存较大的实例 先给个示例代码: import java.util.ArrayList; import java.util.List; imp ...

  4. 火山引擎MARS-APM Plus x 飞书 |降低线上OOM,提高App性能稳定性

    通过使用火山引擎MARS-APM Plus的memory graph功能,飞书研发团队有效分析定位问题线上case多达30例,线上OOM率降低到了0.8‰,降幅达到60%.大幅提升了用户体验,为飞书的 ...

  5. BTrace:线上问题排查工具

    BTrace简介 GitHub地址:BTrace 下载地址:v1.3.11.3 官方使用教程:Btrace使用教程 使用场景 BTrace 是一个事后工具,所谓事后工具就是在服务已经上线了,但是发现存 ...

  6. 记一次线上bug排查-quartz线程调度相关

    记一次线上bug排查,与各位共同探讨. 概述:使用quartz做的定时任务,正式生产环境有个任务延迟了1小时之久才触发.在这一小时里各种排查找不出问题,直到延迟时间结束了,该任务才珊珊触发.原因主要就 ...

  7. 线上问题排查神器 Arthas

    线上问题排查神器 Arthas 之前介绍过 BTrace,线上问题排查神器 BTrace 的使用,也说它是线上问题排查神器.都是神器,但今天这个也很厉害,是不是更厉害不好说,但是使用起来非常简单.如果 ...

  8. JVM 线上故障排查基本操作--CPU飙高

    JVM 线上故障排查基本操作 CPU 飚高 线上 CPU 飚高问题大家应该都遇到过,那么如何定位问题呢? 思路:首先找到 CPU 飚高的那个 Java 进程,因为你的服务器会有多个 JVM 进程.然后 ...

  9. Java架构师线上问题排查,这些命令程序员一定用得到!

    Java架构师线上问题排查,这些命令程序员一定用得到! 线上问题排查,以下场景,你遇到过吗? 一.了解机器连接数情况 问题:1.2.3.4的sshd的监听端口是22,如何统计1.2.3.4的sshd服 ...

随机推荐

  1. time模块和datetime模块详解

    一.time模块 time模块中时间表现的格式主要有三种: a.timestamp时间戳,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量 b.struct_time时间元组,共 ...

  2. luogu2120 [ZJOI2007]仓库建设

    大米饼写的太棒辣qwqqwq #include <iostream> #include <cstdio> using namespace std; typedef long l ...

  3. laravel5.2总结--composer使用和自动加载介绍

    首先看下phpcomposer官方的定义,composer是 PHP 用来管理依赖(dependency)关系的工具.你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer ...

  4. Java之OutOfMemoryError简单分析

    Java之OutOfMemoryError简单分析 最近编码遇到了Java内存溢出的问题,所以就想顺便总结一下几种导致Java内存溢出的栗子,以及碰到Java内存溢出要如何去解决. Java堆溢出 J ...

  5. hibernate 出错 集合

    Lazy="false"反而出错 错误信息: “System.Configuration.ConfigurationErrorsException”类型的异常在 Spring.Co ...

  6. Python+Selenium练习篇之6-利用class name定位元素

    有时候,我们在用firepath(不会的请点这里)查看元素的XPath信息,发现没有可以用来定位的id信息,这个时候我们就需要考虑用其他的可用的来定位元素.本文介绍如何通过元素节点中class nam ...

  7. Python数据分析基础——读写CSV文件2

    2.2筛选特定的行: 行中的值满足某个条件 行中的值属于某个集合 行中的值匹配于某个模式(即:正则表达式) 2.2.1:行中的值满足于某个条件: 基础python版: #!/usr/bin/env p ...

  8. 深入学习之mysql(五)连接查询

    深入学习Mysql(五)连接查询 1.准备数据库: CREATE DATABASE IF NOT EXISTS `db_book2` DEFAULT CHARACTER SET UTF8; USE ` ...

  9. python正则 转

    python中的正则表达式(re模块)   一.简介 正则表达式本身是一种小型的.高度专业化的编程语言,而在python中,通过内嵌集成re模块,程序媛们可以直接调用来实现正则匹配.正则表达式模式被编 ...

  10. Incorrect column count: expected 1, actual 6

    JdbcTemplate使用时出现了一些问题: 解决办法: