4年前 (2021-01-21)  jvm |   抢沙发  2349 
文章评分 0 次,平均分 0.0

如果您只是想在不了解理论的情况下快速解决问题,请跳到第四部分介绍ClassLoader防泄漏库。

我正在计划一系列关于类加载器泄漏的文章,也被称为PermGen内存泄漏。您到达这个页面可能是因为您的javaweb应用程序由于可怕的错误而崩溃java.lang.OutOfMemoryError: PermGen永久空间(或java.lang.OutOfMemoryError: Metaspace元空间,如果您使用的是java8)。

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

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

第一件事:堆转储生成dump文件

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

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

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

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

Classloader类加载器引起元空间Metaspace的内存泄露

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

Classloader类加载器引起元空间Metaspace的内存泄露

选择您的应用程序服务器(确保它不是应用程序服务器引导程序)并单击Finish。

查找泄漏的类加载器

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

Classloader类加载器引起元空间Metaspace的内存泄露

相反,您应该做什么取决于获取堆转储时使用的应用程序服务器。如果您使用的是Caucho's Resin的一个相当新的版本(>=4.0.12),那么您就很幸运了,因为它有一些显著简化查找泄漏的类装入器的特性。Resin做的非常巧妙的一点是,它为每个类加载器添加了一个标记,从Resin的角度来看,这些类加载器已经准备好被垃圾收集了。这允许我们简单地搜索那个标记,并分析为什么标记的类加载器没有被垃圾收集。

因此,单击“打开查询浏览器”图标,然后选择“List objects/with incoming references”。

Classloader类加载器引起元空间Metaspace的内存泄露

现在输入标记的类名,对于版本4.0.12–4.0.20,它被称为com.caucho.loader.ZombieMarker,因为树脂4.0.21它被称为com.caucho.loader.ZombieClassLoaderMarker

Classloader类加载器引起元空间Metaspace的内存泄露

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

Classloader类加载器引起元空间Metaspace的内存泄露

现在您可以跳过本节的其余部分。

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

Classloader类加载器引起元空间Metaspace的内存泄露

类装入器资源管理器如果您不知道应用程序服务器中用于每个web应用程序的类装入器的类名,只需单击“完成”。这将向您提供堆转储中所有类装入器的列表。

Classloader类加载器引起元空间Metaspace的内存泄露

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

不同类型的引用

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

这里有一个“正常”的强引用,这就是你所拥有的,除非你努力去拥有一个较弱的引用。然后是弱引用,您可能已经直接或间接地使用了它,例如通过WeakHashMap。弱引用以这样一种方式工作,即只要不再有对被引用对象的强引用,就可以对其进行垃圾收集。这意味着弱引用本身不会导致内存泄漏。

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

在虚拟机抛出OutOfMemoryError之前,保证已清除对软可访问对象的所有软引用。

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

这给我们留下了虚幻的参考资料。我还没有真正掌握幻象引用,但是它们比弱引用弱,而且据我所知,弱引用甚至不能到达只有幻象引用的被引用对象。相反,虚引用可以与ReferenceQueue一起使用,以便在被引用对象被垃圾收集时得到通知。现在我们只需要知道两件事。1: 你可能永远不会使用任何幻象参考。2: 虚引用不会导致类加载器泄漏。

找到内存漏洞

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

Classloader类加载器引起元空间Metaspace的内存泄露

现在有三件事可以发生:

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

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

Classloader类加载器引起元空间Metaspace的内存泄露

(但是,作为线程的contextClassLoader实际上可能是导致泄漏的原因)。

最后但并非最不重要的一点是,通过查找引用并找到不需要的引用,我们可以找到导致泄漏的原因,从而防止类装入器被垃圾收集。此引用可能在您自己的代码、第三方库、应用程序服务器或JVM中。如果您将JDBC驱动程序放在web应用程序中,而不是放在应用程序服务器级别上,情况就是这样。

Classloader类加载器引起元空间Metaspace的内存泄露

祝你好运找到那些讨厌的classloader漏洞!

更多使用MAT分析内存泄露的文章可参考:使用Eclipse内存分析器工具进行堆转储分析

 

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

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册