3年前 (2020-12-08)  jvm |   抢沙发  612 
文章评分 0 次,平均分 0.0

如何发现内存泄露

如果您是一名新手或中级Java开发人员,还不知道如何使用Java虚拟机(JVM)生产环境,那么在Java应用程序中查找内存泄漏可能是大海捞针。但是,根据您的评测工具,您可以轻松地分析Java内存消耗,同时获得对Java生产应用程序中堆的即时洞察。但是,在详细介绍如何在java web应用程序中发现内存泄漏之前,让我们先了解一下什么是java内存泄漏,这种泄漏的可能原因以及处理此问题的修复过程。

Java内存泄漏

内存泄漏只是由PermGen中保留的引用链接引起的,不能被垃圾回收。

听起来像胡言乱语,对吧?好吧,保持冷静,我继续解释。因为web容器使用类到类装入器映射系统来隔离web应用程序,而且因为类是由其名称和加载它的类装入器唯一标识的。因此,可以有一个同名的类,在一个JVM中加载多次,每个类都有一个不同的类装入器。

  • 对象保留对类的引用(java.lang.Class)它的实例。
  • 类又保留了对加载它的类装入器的引用。
  • 类装入器保留对它加载的每个类的引用。

从长远来看,这可能会成为一个非常大的参考图。别忘了这些类直接加载到PermGen中。因此,从web应用程序中保留对特定对象的引用会将web应用程序加载到PermGen中的每个类固定下来。即使web应用程序被重新加载后,这些引用通常仍会保留,每次重新加载时,更多的类会被固定或卡在PermGen中,而PermGen在适当的时候会被填满。

什么是PermGen?

PermGen是永久生成(PermGen)的缩写,是JVM中的一个堆,专门用于存储JVM的Java类的内部表示形式,以及被扣留的字符串实例。简单地说,它是一个独立于主Java堆的独占堆位置,JVM在那里注册与已加载类相关的元数据。

不过,大多数java servlet容器和WebSocket技术都支持org.apache.catalina.core.JreMemoryLeakPreventionListener通过扩展(如ApacheTomcat7.0及更高版本)初始化。尽管如此,如果出现更复杂的情况,比如重新加载时的PermGen错误和应用程序本身引起的bug干扰,那么包含这个内存泄漏处理程序是不够的。当tomcat servlet没有导致泄漏时,这会变得更有趣,应用程序也不是(至少不是直接的),而是JRE代码中由第三方库触发的bug。

随着Java开发工具包JDK6(更新7或更新版本)的出现,JDK附带了一个方便的工具,它使我们的生活变得更加轻松。这个工具称为VisualVM;它是一个图形化工具,可以无缝地连接到任何JVM,除了允许您导航该堆之外,还允许您从JVM堆中排出不需要的垃圾。在tomcat servlet中,web应用程序的类加载器也是一个名为org.apache.catalina.loader.WebappClassLoader. 因此,如果我们的Tomcat实例只部署了一个web应用程序,那么堆中应该只有一个此类的实例。但如果有更多的话,我们就有漏洞了。

如何在java web应用程序中查找内存泄漏的步骤

现在我们已经解决了这个问题,让我们快速深入了解如何检测和避免Java内存泄漏的步骤。让我们立即深入研究如何使用VisualVM来解决这个问题。

步骤:

1. 打开命令提示符终端,输入下面的命令启动visualvm;

${javahome}/bin/jvisualvm

将弹出一个类似于图1的窗口。

如何在java web应用程序中发现内存泄漏

图1:JavaVisualVM

如何发现内存泄露

右键单击左侧边栏中的Tomcat,然后选择“Heap Dump”。

如何在java web应用程序中发现内存泄漏

如何发现内存泄露

图2:堆转储:单击“OQL控制台”按钮。

点击控制台顶部的“转储”按钮。这将打开一个控制台,允许您查询堆转储。对于本练习,我们希望找到org.apache.catalina.loader.WebappClassLoader.So在结果控制台中输入以下命令;

select x from org.apache.catalina.loader.WebappClassLoader x

如何发现内存泄露

图3:“OQL查询编辑器控制台”:输入上面的命令并单击execute

在本例中,VisualVM发现了两个web应用程序类装入器实例;一个用于web应用程序本身,另一个用于Tomcat管理器应用程序。

使用Tomcat管理器应用程序在重新启动web应用程序http://localhost:8080/manager/html并获取另一个Tomcat进程的堆转储。

如何发现内存泄露

图4:重新启动web应用程序并导航回“OQL控制台”以再次重复步骤3

注意上面步骤中包含了一个额外的实例。这是因为这3个实例中的一个应该由垃圾回收器收集,但事实并非如此。多亏了Tomcat,我们可以很容易地分辨出哪个实例没有被垃圾回收,因为所有活动类装入器都设置了字段名:“started”设置为“true”。

为了找到无效的实例,请单击每个类装入器实例,直到找到“started”字段设置为“false”的实例。

如何发现内存泄露

图5:单击堆转储中的每个类装入器实例,找出有问题的实例-“started”字段设置为false

现在我们已经找到了导致泄漏的类装入器,我们需要确定哪个对象持有对类装入器的引用。显然,大量的对象将被许多其他对象引用,但从本质上讲,只有有限数量的这些对象会构成引用图的根。这些是我们特别感兴趣的对象。

因此,在instances选项卡的底部窗格中,右键单击构成引用图根的对象实例,然后选择“Show Nearent GC root”。

结果窗口应该是这样的;

如何发现内存泄露

图6:右键单击构成引用图根的对象实例,然后选择“Show Nearent GC root”。

右键单击实例并选择“显示实例”。

如何发现内存泄露

图7:右键单击实例并选择“Show Instance”。

由此,我们可以推断这是sun.awt.AppContext类型。我们还可以看到AppContext中的contextClassLoader字段包含对WebappClassLoader的引用。因此,这是导致内存泄漏的错误引用。现在我们来看看是什么实例化了sun.awt.AppContext类型,对于初学者。

首先,我们用以下代码在调试模式下重新启动Tomcat;

export JPDA_SUSPEND=y

${TOMCAT_HOME}/bin/catalina.sh jpda

然后,我们继续远程调试类加载序列——在这种情况下,我将使用Eclipse来完成这项工作。另外,我们需要在sun.awt.AppContext使用Open Type命令(Shift+Control+T)导航到sun.awt.AppContext类型。

右键单击大纲窗格中的类名并选择Toggle Class Load Breakpoint“切换类加载断点”。

此后,我们需要通过将调试器连接到Tomcat实例并让调试器在sun.awt.AppContext装载;

将调试器连接到Tomcat实例,并将调试器设置为在sun.awt.AppContext已加载。

就这样!它已经被JavaBeans框架实例化,在这个实例中,Oracle Universal Connection Pool (UCP)正在使用它。因此,我们可以注意到contextClassLoader是一个最终的字段,它看起来像是一个单独的字段;所以我们可以假设这个字段只在AppContext的实例化过程中设置一次。

所以我们可以推断这个字段只在实例化期间设置了一次。

我很方便地将上面的代码添加到我的servlet上下文侦听器中,使它在应用程序启动期间执行,并达到了修复这个特定内存泄漏的预期效果。

如何在Java Web应用程序中发现内存泄漏-小结

总而言之,JEE应用程序的“PermGen”内存不足错误通常驻留在应用程序本身(或应用程序使用的库)中,并且通常由JRE库中的类组成,这些类包含对web应用程序类加载器的引用或web应用程序类加载器实例化的对象的引用。

查找泄漏原因的整个过程是利用Java应用程序性能监视(APM)解决方案。就像Fusion Reactor的堆分析器为未收集的web应用程序类装入器实例提供源代码,然后为直接或间接保留类装入器的根GC对象提供源。当您找到这个对象时,您现在可以使用APM的内置调试器来发现这个对象是如何被实例化的,然后设计一种方法来修改它的常规行为。这样做是为了使它不会永远保持类装入器引用。
如何发现内存泄露

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/966.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册