Memory Leak Due To Improper Exception Handling
https://dzone.com/articles/me...
译:祝坤荣
本文中,咱们会探讨到咱们在生产环境遇到的内存问题以及如何解决的。这个利用会在运行几个小时候后无响应。但并不分明什么导致了利用无响应。
技术栈
这个利用运行在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剖析报告。
图1:大对象报告
能看到yCrash指出“org.apache.logging.log4j.LogManager”是内存中最大的对象。对象占用了总内存的98.2%. 其余对象占用不到2%的内存。以下是这个最大对象的对象树:
图2:对象援用树
看下对象树中红箭头标的中央。这是利用的起始代码。图2中局部包名被遮蔽了避免能看出具体利用。你能看到这个对象包名为“xxxxxxxx.superpower.Main$1.val$hprofParser”占用了98.2%的内存。
利用有个类叫“xxxxxxxxxxxxxxx.Main.”。很显著泄露来自这个Main对象。不过,也看不出"xxxxxxxxxxxxxxx.Main$1”是什么。“$1”指出了这是"xxxxxxxxxxxxxxx.Main”类的第一个匿名外部类。匿名外部类是指你能够在父类中定义一个不必命名的外部类。但这不是一个宽泛应用的Java编程实际。不过匿名外部类岂但影响了程序的可读性也导致定位艰难。
以下是“xxxxxxxxxxxxxxx.Main”的高层概要源码。为了缩小乐音和改良可读性,类中的无关代码都被移除了。
图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)不会被回收。它会导致内存泄露。
解决方案
在大多数性能问题里,定位问题的根因很艰难。修复它们很简略。
这里就是没有异样。
图4:修复内存泄露的源码
咱们将pm.stopReporting()办法移到了finally中。在Java语言中,放在finally代码块中的代码无论会不会抛异样都会执行。finally块的内容能够在这里https://docs.oracle.com/javas...理解下。这样,即便hprofParser.read()办法抛了异样,pm.stopReporting办法仍会被调用,让线程终结。如果线程被终结,在垃圾回收时所有对象的援用就会被回收。
当改变后,问题立即解决了。
本文来自祝坤荣(时序)的微信公众号「麦芽面包」,公众号id「darkjune_think」
开发者/科幻爱好者/硬核主机玩家/业余翻译
转载请注明。
交换Email: zhukunrong@yeah.net