异常处理不当导致的内存泄漏https://dzone.com/articles/me...译文:朱坤荣在这篇文章中,我们将讨论我们在生产环境中遇到的内存问题以及如何修复。该应用程序在运行数小时后变得无响应。但目前尚不清楚是什么导致应用程序变得无响应。技术栈本应用运行在AWS云中规格为r5a.2xlarge的EC2实例上。该应用程序使用Spring框架在ApacheTomcat服务器上运行。它还使用S3和ElasticBeanstalk等AWS服务。该应用程序使用较大的堆大小(-Xmx):48GB。定位我们使用yCrash工具来定位这个问题。我们让应用程序运行流量15分钟。然后在此应用程序上执行yCrash脚本。yCrash脚本从应用程序堆栈中捕获数据,对其进行分析,并显示问题的根本原因。yCrash脚本捕获的数据包括:垃圾收集日志、线程转储、堆转储、netstat、vmstat、iostat、top和ps。yCrash分析了材料并生成了内存泄漏报告。以下是yCrash生成的heapdump分析报告。图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”表示这是“xxxxxxxxxxxxxxxx.Main”类的第一个匿名内部类。匿名内部类是指可以在父类中定义一个未命名的内部类。但这并不是广泛使用的Java编程实践。但是,匿名内部类不仅影响程序的可读性,而且难以定位。以下是“xxxxxxxxxxxxxxx.Main”的高级摘要源代码。为了减少噪音并提高可读性,类中的无关代码已被删除。图3:导致内存泄漏的源码可以看到第九行是匿名内部类。该类继承了PrintingProgressMeter类。PrintingProgressMeter类继承自java.util.Thread。任何类无论继承java.util.Thread,都会成为一个线程。第20行,通过pm.start()方法启动了PrintingProgressMeter线程;在第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”开发者/科幻爱好者/硬核主机玩家/素人翻译,转载请注明。通讯邮箱:zhukunrong@yeah.net
