HeapDump 助力之道

本文是我同事郭大侠在2018年12月5号发表与公众号“CI智创未来”的一篇文章,特为大家转载分享。

 

HeapDump 日常

        应用宕了、应用挂了、应用over了…… 应用发生heapdump了,这是一个IT运维日常面临的问题,之后项目经理、开发人员、运维人员又开始了一次小型hp战争,久而久之每一次的heapdump代表了大家口中述说的应用故障。但是HeapDump == 应用故障?

        话可不能这么绝对,HeapDump是无辜的。

 

HeapDump 是啥?

        

        先介绍下HeapDump也叫堆转储文件,是一个Java进程在某个时间点上的内存快照,通常以文件形式保存在磁盘,它仅仅是一个默默无闻的文件!heapdump总体上在触发快照的时候都保存了java对象和类的信息。

        heapdump包含的信息:

    1. 所有对象信息:类、变量、直接量及引用值;

    2. 所有类信息:类加载器、类名、超类及静态变量;

    3. GCR(Garbage Collections Roots):可以直接被JVM触及的对象;

    4. 线程栈及本地变量:获取快照时的线程栈信息,以及局部变量的详细信息

        我们可以在上面这些heapdump信息的帮助下分析如下类型的问题:

    1. 找出内存泄漏的原因;(主要作用)

    2. 找出重复引用的jar包或类;(主要作用)

    3. 分析集合的使用合理性;

    4. 分析类加载器;

        通过对heapdump的分析就是对应用的内存使用进行分析,从而更加合理地使用内存,帮助应用进行优化升级。所以先正下名,HeapDump是正义的!heapdump有这么多好处,为什么经常会成为应用宕机的曲解呢?因为其主要的发生状况之一:内存泄露,简称OOM。

HeapDump 什么时候会发生?

 

        接下来的内容我们来讲讲大家平时工作中经常会遇到的一些OOM发生的场景和救治办法(咱不讲什么线程之类的,那不易看懂)。

        场景一:小马拉大车,这分两说

    • 小马情况 - 内存设置过低,一般会引发高频率的GC而导致OOM,不过这个几率不大。通常web应用内存分配2G还不够,调大也不是长久的办法,因为内存基本有其他场景的问题了。解决的话,就是修改JVM启动参数,直接增加内存。这一点看上去似乎很简单,但很容易被忽略。JVM默认使用的内存为64M,Tomcat默认使用的内存为128MB,对于稍复杂一点的系统就会不够用。在某项目中,就因为启动参数使用的默认值,经常报OOM错误。因此,-Xms,-Xmx参数一定不要忘记加;

    • 大车情况 – 又称万精油应用,特点是1拖N应用,在一个应用中包含了大量的子系统和功能,这加剧了应用常规对内存的消耗,一旦临时出现内存高消耗的操作即会引发heapdump。一个字“拆”,怎么“拆”不是本篇话题

 

        场景二:查询/导出大量数据,这个有好多情况了

    • 海量数据的情况下,SQL查询条件没做限制,啥都能查导致啥都查不了。当然内存加到2T并且耐心等待N小时可能会有结果,好坏看运气。就算你查出来了,第二种情况导致功亏一篑。一般来说,一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据快速增长,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式。

    • SQL查询动作未做重复提交的限制,再小的查询结果遇上大量并发也得宕。简单的页面遮罩可以有效解决问题;

    • 远程调用传输大量数据,常见于接口返回大量数据,一般的http协议传输已经很慢了,再来大量数据产生大量不可释放的内存消耗导致heapdump。大量数据传输还是交给文件处理,或者kafka等高可靠的消息中间件吧。

 

        场景三:数据没能及时释放,可能是处理较多的数据或较长的处理流程

    • 上传并处理数据,没有分批分段处理或延后处理;

    • 输出数据并下载,常见是excel报表的下载。

    • excel导出使用了低效的HSSFWorkbook或者XSSFWorkbook,我们建议以后改成SXSSFWorkbook,一是可以防止heapdump,二是可以不限制数量进行导出。

        以上几个场景当你看到下面的关键字的时候它就是堆栈溢出了:

HeapDump 助力之道

        也就是当你看到heap相关的时候就肯定是堆栈溢出了,此时如果代码没有问题,适当调整-Xmx和-Xms是可以避免的。其它的话要么代码有问题,要么访问量太多并且每个访问的时间太长或者数据太多,导致数据释放不掉,因为GC是要找到那些是垃圾才能回收,这里它不会认为这些东西是垃圾,自然不会去回收了;注意这个溢出之前,可能系统会提前先报错关键字为:

HeapDump 助力之道

这种情况是当系统处于高频的GC状态,而且回收的效果依然不佳的情况,就会开始报这个错误,这种情况一般是产生了很多不可以被释放的对象,有可能是引用使用不当导致,或申请大对象导致。

 

继续场景四,第三方jar包多(就这一句话)

第二类内存溢出,PermGen的溢出,你会看到这样的关键字:

HeapDump 助力之道

         原因:应用的代码非常多或引用的第三方包非常多、或代码中使用了大量的静态常量、或者通过动态代码加载等方法,导致常量池的膨胀,所以在面对这种情况常用的手段是:增加-XX:PermSize和-XX:MaxPermSize的大小。还有好建议是找个靠谱的程序员!

             

HeapDump 的预防

        

        如果你耐心看到这,我们再来说说系统设计,这和heapdump没什么直接关系,但是一个经常heapdump的系统很有可能不是技术手段能解决的,那是整体设计出现了问题。

        当一个系统诞生初期,其功能、数据量、用户、接口、后台任务、报表的数量都在预计的规划中,少量合理的资源即可满足运行要求,在考虑到项目的上线时间以及开发便捷统一的情况下,以上各种功能通常被黏合在一起,这并没有问题。但随着系统进入正式运行,功能、数据量、用户首先快速上升(这一般是数据快速累计的时期)提高了内存消耗率,这时期用户一般不会有感觉慢,但运维监控的数据应该能反应一定的问题,要重视!接踵而来的是后台任务和报表要求提高的情况出现(数据产出价值的时期),原本合理的资源变得捉襟见肘,内存的消耗进一步的提高并且长时间处于高消耗模式,常常用户操作+后台任务+报表统计任意二者的波峰重叠易引发heapdump。怎么办?加内存呗!

        短期增加内存是个好办法,能够迅速解决问题,这种操作成本低风险低,无需更改程序并测试,如果该应用在生命周期后期这么做太棒了。可一个处于数据累计或产出价值时期的应用,简单的增加资源不能彻底解决问题,仅仅延后了heapdump爆发的时间,并在最终需要解决问题的时候浪费了之前投入的资源,这时候需要考虑架构改造了。

        那么改造考虑的首要因素是什么?业务/功能分离呗。先处理高耗时耗内存的后台任务?未必!后台任务多数是定时可估量的,合理的安排可以规避多数产生heapdump的风险点。我们应该先考虑转移报表,先是实时报表,其次是统计类报表。实时报表说小了是一次简单查询,但更多的是一个随时会被触发的高耗时耗内存的“后台任务”,并且“看报表” 这个动作往往发生在业务繁忙的时间段,这急剧增加了heapdump的可能性,我们应当首先迁移。而且报表无需具备交互的特性,“实时报表”被转为“异步实时报表”更为合理,将报表的计算、页面生成、数据填充放在另外可靠的服务器上操作,尽量减少web应用服务器的资源消耗和网络消耗,这样可以有效降低heapdump的发生。另外成熟的商用报表产品应当被充分考虑,以适应不同的使用场景。

结束语

        

        最后简单推荐一款heapdump文件的分析工具以便大家使用。

        IBM的HeapAnalyzer是非常重要的heapdump分析工具。它通过启发式搜索引擎和分析Java heap帮助我们找到一个可能的泄漏区域。一般来说分析图形报告中内存消耗比大的java对象可快速定位问题的位置。安装文件是一个ha456.jar直接用jre打开即可。

HeapDump 助力之道

         此款工具唯一的不足之处是使用期间内存消耗巨大,对机器的硬件有较高的要求。

        就说这些,希望heapdump的知识能够为系统的升级演进贡献一点力量,祝您的应用健康运行稳步提升,期待下次和您分享经验。