4年前 (2020-09-23)  爪哇岛 |   抢沙发  1775 
文章评分 1 次,平均分 4.0

你可能是因为您的javaweb应用程序在java.lang.OutOfMemoryError:PermGen space(或java.lang.OutOfMemoryError:Metaspace,如果您使用Java 8)。我不会解释这个错误意味着什么,也不会解释它发生的原因,因为网上有很多关于它的信息——例如,请参阅Frank Kieviet关于这个问题及其解决方案的博客。

在第一篇文章中,我将关注的是“什么”和“如何”之间的一步,即在其他在线讨论中经常被遗忘的“地点”。在您意识到存在类加载器泄漏之后,必须先确定这些泄漏在哪里,然后才能修复它们。

就在几年前,找到类加载器泄漏的源头确实很棘手——至少我是这么认为的。手头的工具是jmap和jhat,它们相当“原始”。后来出现了一些商业工具,比如YourKit来帮助你。现在有一些开源的替代品,使得找到有问题的代码变得相对容易。我会一步一步教你怎么做。

第一件事:heap dump堆转储

找到类加载器泄漏需要做的第一件事是获取堆转储进行分析。堆应该在至少一个类加载器实例泄漏之后被转储,这样您就可以分析对泄漏实例的引用,从而防止它被垃圾回收。

最简单的方法之一是添加一个JVM参数,使(Sun/Oracle)JVM在java.lang.OutOfMemoryError发生。这样做的好处是,你不必试图强迫泄漏的出现,以防你不知道是什么触发了它。这也意味着你不会花时间去寻找一个没有漏洞的堆。

参数的名称是-XX:+HeapDumpOnOutOfMemoryError,所以将-XX:+HeapDumpOnOutOfMemoryError添加到命令行、脚本或配置文件中–这取决于您使用的应用程序服务器以及如何启动它。然后运行并重新部署应用程序,直到它与java.lang.OutOfMemoryError:PermGen space/Metaspace。文件名将类似于java_pid18148.hprof,它将位于应用程序服务器的启动目录中,该目录可能与启动启动脚本的目录不同。您还可以使用-XX:HeapDumpPath=/directory参数自己决定目录。

现在已经有了堆转储,请下载Eclipse内存分析器(MAT),运行它并打开刚才获取的堆转储。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

另一种方法是从本地运行的应用程序服务器中从MAT中提取堆转储。只需启动MAT并从“文件”菜单中选择“Aquire Heap Dump…”。这将为您提供一个正在运行的Java应用程序的列表。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

选择您的应用程序服务器(确保它不是应用程序服务器引导程序),然后单击完成。

找到泄漏的类加载器

当您打开或获取堆转储时,MAT会询问您是否要对转储执行某种分析,例如查找内存泄漏嫌疑犯。这可能有助于查找堆泄漏,但根据我的经验,在类加载器泄漏方面没有太大帮助,因为泄漏的类加载器的保留(非类)对象通常少于当前的“未泄漏”对象。因此,我建议您单击“取消”。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

您应该做什么,取决于您在获取堆转储时使用的应用程序服务器。如果您使用的是最近版本的Caucho's Resin(>=4.0.12),那么您就走运了,因为它有一些特性可以显著简化查找泄漏的类加载器。它为每个类加载器添加了一个标记,这使得我们可以简单地搜索该标记并分析为什么标记的类加载器没有被垃圾回收。

所以点击“打开查询浏览器”图标,选择“List objects列表对象”/“with incoming references带传入引用”。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

现在输入标记的类名,对于Resin version 4.0.12–4.0.20称为com.caucho.loader.ZombieMarker,由于Resin 4.0.21被称为com.caucho.loader.ZombieClassLoaderMarker。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

单击Finish将显示一个僵尸标记实例的列表,Resin认为可以进行垃圾收集的每个类加载器都有一个实例。您可以通过单击前面的小箭头来查看每个类的类加载器,该箭头将展开传入的引用。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

我不知道是否有其他应用服务器提供了类似于Resins僵尸标记的东西,但是假设你的服务器没有,你应该这样做:点击“openquerybrowser”图标,然后选择“javabasics”/“Class Loader Explorer”。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

除非您已经知道应用服务器中每个web应用程序所使用的类加载器的类名,否则只需单击Finish。这将为您提供堆转储中所有类加载器的列表。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

希望您可以通过类名来找出哪些是(可能泄漏的)web应用程序实例。对于每个这样的实例,您需要执行下面查找泄漏的步骤,以确定该实例是否是泄漏实例。

不同类型的参考文献

如你所知java.lang.OutOfMemoryError:PermGen space/Metaspace是指旧的、未使用的类加载器不会被垃圾回收,它们没有被垃圾回收的原因是,有一个来自类加载器外部的引用,要么指向由该类加载器加载的类(包括此类类的任何实例),要么指向类加载器本身。您可能不知道,Java中实际上有四种不同类型的引用。在找到你的类加载器漏洞之前,我想我应该花点时间简单解释一下。

java8 metaspace科普:https://javakk.com/444.html

有一个“正常”的强参照,这就是你所拥有的,除非你努力去拥有一个较弱的参照。还有一个weak reference弱引用,您可能直接或间接地使用了它,例如通过WeakHashMap。弱引用的工作方式是这样的,当被引用对象不再有强引用时,它可能被垃圾回收。这意味着弱引用本身不会导致内存泄漏。

不久前,我还学习了软引用和虚引用。软引用比弱引用强。对象不会被垃圾回收,即使对它的唯一引用是软引用。软引用的意思是,每当JVM即将耗尽内存时,作为最后的手段,它将垃圾收集只有软引用(而且可能较弱)的所有对象。的JavaDocjava.lang.ref.SoftReference说

在虚拟机抛出OutOfMemoryError之前,所有对软访问对象的软引用都将被清除。

JavaDoc没有显式地说明这是只适用于堆上的普通对象,还是也适用于PermGen空间中的类。在调查一个混合使用软引用的类加载器泄漏时,我下载了jdk1.6源代码,并试图通过研究来找出问题所在。我从消息来源得到的结论——它不适用于PermGen/类分配——与后来的测试结果相反……我仍然不确定这是如何工作的,但因为对我来说“时间长,没有C语言”,我倾向于相信软引用对象在java.lang.OutOfMemoryError:PermGen空间被抛出。

我甚至问过Oracles GC开发团队的一个成员,他不能给出一个直接的答案…

这给我们留下了Phantom references。我还没有真正掌握Phantom references虚引用,但是它们比弱引用弱,据我所知,弱到你甚至不能到达只有一个虚引用的被引用对象。相反,虚引用可以与ReferenceQueue一起使用,以便在被引用对象被垃圾回收时得到通知。现在我们只需要知道两件事。

  1. 你可能永远不会使用任何Phantom references虚引用。
  2. Phantom references虚引用不会导致类加载器泄漏。

找到漏洞

现在,要找出类加载器泄漏的原因,请右键单击上面找到的一个类加载器—要么是应用服务器已标记为准备进行垃圾回收的类加载器(在这种情况下,只需右键单击僵尸标记本身),要么是可能泄漏的类加载器。如果你在“类加载器资源管理器”中,你需要首先选择“类装入器”,然后选择“路径到GC根”,然后,由于(假设)只有强引用会导致类装入器泄漏,所以选择“排除所有虚/弱/软等引用”。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

现在可能会发生以下三种情况之一:

我见过一些根本找不到有力证据的案例。在这种情况下,类加载器应该被垃圾回收。我现在不想讨论为什么不是这样,但可能会回来大声嚷嚷。现在,要知道这不是你的错,你也无能为力。

如果您没有使用Resin的僵尸标记功能(或其他应用服务器中的类似功能),您可能会找到一个完全合法的强引用。例如,类加载器可能是当前正在执行的线程的contextClassLoader,例如来自应用服务器线程池的线程,为HTTP请求提供服务。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

最后但并非最不重要的是,我们可以通过查看引用并找到阻止类加载器被垃圾收集的不需要的引用来找到泄漏的原因。此引用可能在您自己的代码、第三方库、应用服务器或JVM中。如果您将JDBC驱动程序放在web应用程序中,而不是在应用程序服务器级别,那么它将是这样的。

如何使用Eclipse内存分析器MAT来查找类加载器泄漏

 

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

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册