造成内存泄漏的异常处理

Memory Leak Due To Improper Exception Handling

原文:https://dzone.com/articles/memory-leak-due-to-improper-exception-handling

译:祝坤荣

ba176fe81cebe628679292d802144289.png

 Pexels 上的 Andrea Vera Sasso 拍摄的图片

本文中,我们会讨论到我们在生产环境遇到的内存问题以及如何解决的。这个应用会在运行几个小时候后无响应。但并不清楚什么导致了应用无响应。

技术栈

这个应用运行在AWS云的规格为r5a.2xlarge的EC2实例。这个应用运行在使用Spring框架的Apache Tomcat服务器。它也用像S3和Elastic Beanstalk这样的AWS服务。应用用了个大heap size(-Xmx):48GB。

定位

我们用yCrash工具来定位这个问题。我们让应用跑15分钟流量。然后在这个应用上执行yCrash脚本。yCrash脚本从应用栈上捕捉了360度数据,分析它们,并展示了问题的根因。yCrash脚本捕捉的数据包括:Garbage Collection日志,线程dump,heap dump,netstat,vmstat,iostat,top和ps。

yCrash分析材料生成了一份内存泄露报告。下面是yCrash生成的heap dump分析报告。

83c8997d28be313bc63fa5cea7cc0774.png

图1:大对象报告

能看到yCrash指出“org.apache.logging.log4j.LogManager”是内存中最大的对象。对象占用了总内存的98.2%. 其他对象占用不到2%的内存。以下是这个最大对象的对象树:

870fb53c38b7257143078c32e8572b5c.png

图2:对象引用树

看下对象树中红箭头标的地方。这是应用的起始代码。图2中部分包名被遮盖了防止能看出具体应用。你能看到这个对象包名为“xxxxxxxx.superpower.Main$1.val$hprofParser”占用了98.2%的内存。

应用有个类叫“xxxxxxxxxxxxxxx.Main.”。很明显泄露来自这个Main对象。不过,也看不出"xxxxxxxxxxxxxxx.Main$1”是什么。“$1”指出了这是"xxxxxxxxxxxxxxx.Main”类的第一个匿名内部类。匿名内部类是指你可以在父类中定义一个不用命名的内部类。但这不是一个广泛使用的Java编程实践。不过匿名内部类不但影响了程序的可读性也导致定位困难。

以下是“xxxxxxxxxxxxxxx.Main”的高层概要源码。为了减少噪音和改进可读性,类中的无关代码都被移除了。

4d82dc0d7e83bec1061dd137495fd885.png

图3:导致内存泄露的源码

能看到第九行就是匿名内部类。此类继承了PrintingProgressMeter类。PrintingProgressMeter类继承了java.util.Thread。无论任何类继承了java.util.Thread,都会成为一个线程。

在第20行,PrintingProgressMeter线程是被pm.start()方法启动的;在21行,调用了hprofParser.read()的方法;而在22行,用pm.stopReporting()方法停止了线程。这代码看着很正常,对吗?应用的什么可以触发一个内存泄露呢?

问题:异常处理

在21行hprofParser.read()里有特定场景可能会抛异常。如果一个异常抛出,22行的pm.stopReporting()就不会被调用。如果这行代码不被调用,线程就会永远运行不会退出。如果线程不退出,线程和对象的引用(比如hprofParser)不会被回收。它会导致内存泄露。

解决方案

在大多数性能问题里,定位问题的根因很困难。修复它们很简单。

这里就是没有异常。

dbe55e61993dca9702dbb6d54f685b1e.png

图4:修复内存泄露的源码

我们将pm.stopReporting()方法移到了finally中。在Java语言中,放在finally代码块中的代码无论会不会抛异常都会执行。finally块的内容可以在这里https://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html了解下。这样,即使hprofParser.read()方法抛了异常,pm.stopReporting方法仍会被调用,让线程终结。如果线程被终结,在垃圾回收时所有对象的引用就会被回收。

当改动后,问题立刻解决了。


本文来自祝坤荣(时序)的微信公众号「麦芽面包」,公众号id「darkjune_think」

开发者/科幻爱好者/硬核主机玩家/业余翻译

转载请注明。

交流Email: zhukunrong@yeah.net

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值