共计 1771 个字符,预计需要花费 5 分钟才能阅读完成。
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