接下来是对Java应用程序中特定类型的内存问题的实用介绍。 即–我们将分析导致java.lang.OutOfMemoryError:PermGen空间的错误 堆栈跟踪中的症状。
首先,我们将介绍理解该主题所需的核心概念,并说明什么是对象,类,类加载器和JVM内存模型。 如果您熟悉基本概念,则可以直接跳到下一部分,在此我将描述所讨论错误的两种典型情况以及解决它的提示和建议。
对象,类和类加载器
好吧,我不会从最基本的内容开始。 我想如果您已经找到我们,那么您应该熟悉Java中的一切都是Object的概念。 并且所有对象均由其类指定。 因此,每个对象都有对java.lang.Class实例的引用,该实例描述了该对象的类的结构。
但是,当您在代码中创建一个新对象时,实际上发生了什么呢? 例如,如果您写的东西真的很复杂
人老板=新人()
Java虚拟机(JVM)需要了解要创建的对象的结构。 为此,JVM查找名为Person的类。 而且,如果在程序的特定执行期间第一次访问Person类,则通常必须从JVM从相应的Person.class文件中加载它。 在驱动器上查找Person.class文件,将其加载到内存中并解析其结构的过程称为类加载 。 确保正确的类加载过程是ClassLoader的责任。 ClassLoader是java.lang.ClassLoader类的实例,Java程序中的每个类都必须由某个ClassLoader进行加载。 结果,我们现在具有以下关系:
从下图可以看到,每个类加载器均包含对其已加载的所有类的引用。 就本文而言,这些关系非常有趣。
记住此图像,稍后我们将需要它。
永久世代
如今,几乎每个JVM都使用一个单独的内存区域(称为Permanent Generation(简称PermGen ))来保存Java类的内部表示形式。 PermGen还用于存储更多信息-如果您有兴趣,请从这篇文章中查找详细信息-但对于我们的文章,可以安全地假设仅类定义存储在PermGen中。 在运行Java 1.6的两台计算机上,该区域的默认大小不是非常可观的82MB。
正如我在之前的一篇文章中所解释的那样,Java中的内存泄漏是一种情况,其中某些对象不再被应用程序使用,但是垃圾收集器无法将其识别为未使用。 如果那些未使用的对象对堆使用的贡献很大,以致于应用程序无法满足下一个内存分配请求,则会导致OutOfMemoryError 。
java.lang.OutOfMemoryError:PermGen空间的根本原因是完全相同的:JVM需要加载新类的定义,但是PermGen中没有足够的空间来执行此操作–那里已经存储了太多的类。 可能的原因是,您的应用程序或服务器使用了太多的类,而PermGen的当前大小无法容纳它们。 另一个常见原因可能是内存泄漏。
永久泄漏
但是,仍然有可能在PermGen中泄漏某些东西吗? 它保存着Java类的定义,它们不能成为未使用的,可以吗? 实际上,他们可以。 如果将Java Web应用程序部署到应用程序服务器中,则在取消部署应用程序时,EAR / WAR中的所有这些类将变得毫无用处。 由于应用程序服务器仍处于活动状态,因此JVM继续运行,但是不再使用大量的类定义。 并且应该将它们从PermGen中删除。 如果没有,我们将在PermGen区域发生内存泄漏。
作为一个很好的例子,Tomcat开发人员已经建立了一个Wiki页面,描述了在Apache Tomcat 6.0.24及更高版本中发现并修复的各种漏洞。
泄漏线程
类加载器泄漏的一种可能情况是长时间运行的线程。 当您的应用程序或您的应用程序使用的第三方库(通常以我的经验)启动某个长时间运行的线程时,就会发生这种情况。 一个例子就是计时器线程,其任务是定期执行一些代码。
如果该线程的预期寿命不确定,我们将直接陷入麻烦。 当应用程序的任何部分启动线程时,必须确保它不会使应用程序寿命更长。 在典型情况下,开发人员要么不了解此责任,要么干脆忘了编写清理代码。
否则,如果在取消部署应用程序后某个线程继续运行,则通常将保留对由其启动的Web应用程序的类加载器的引用,称为上下文类加载器 。 反过来,这意味着未部署的应用程序的所有类都继续保留在内存中。 补救? 如果是您的应用程序启动了新线程,则应在取消部署期间使用servlet上下文侦听器将其关闭。 如果它是第三方库,则应搜索其自己的特定关闭挂钩。 或提交错误报告(如果没有)。
驱动程序泄漏
数据库驱动程序可能导致泄漏的另一种典型情况。 我们在Plumbr附带的演示应用程序中遇到了此泄漏。 它是随Spring MVC一起提供的经过稍微修改的Pet Clinic应用程序。 让我们重点介绍将应用程序部署到服务器时发生的一些事情。
- 服务器创建一个新的java.lang.Classloader实例,并开始使用它加载应用程序的类。
- 由于PetClinic使用HSQL数据库,因此它将加载相应的JDBC驱动程序org.hsqldb.jdbcDriver
- 此类是一种很好的JDBC驱动程序,根据JDBC规范的要求,在初始化期间将其自身注册到java.sql.DriverManager 。 该注册包括在DriverManager的静态字段中存储对org.hsqldb.jdbcDriver实例的引用。
现在,当从应用程序服务器取消部署应用程序时, java.sql.DriverManager仍将保留该引用,因为HSQLDB库,Spring框架或应用程序中都没有删除该代码的代码! 如上文所述, jdbcDriver对象仍然持有对org.hsqldb.jdbcDriver类的引用,而该类又持有对用于加载应用程序的java.lang.Classloader实例的引用。 现在,该类加载器仍引用应用程序的所有类。 对于我们特定的演示应用程序,在应用程序启动期间将加载近2000个类,在PermGen中大约占用10MB。 这意味着需要大约5到10个重新部署才能用默认大小填充PermGen,以达到java.lang.OutOfMemoryError:PermGen空间崩溃。
如何解决? 一种可能性是编写一个Servlet上下文侦听器,该侦听器在应用程序关闭期间从DriverManager注销HSQLDB驱动程序。 这很简单。 但是请记住–您将必须使用驱动程序在每个应用程序中编写相应的代码。
使用我们的演示应用程序下载我们最新版本的Plumbr,并使用它来查找泄漏的发生方式,Plumbr如何发现泄漏以及如何解释原因。
结论
您的应用程序可能遇到java.lang.OutOfMemoryError:PermGen space的原因很多。 导致它们大多数的根本原因是对由应用程序的类加载器加载的对象或类的引用,这些对象或类在此之后死亡。 或直接链接到类加载器本身。 对于大多数这些原因,您需要采取的补救措施非常相似。 首先,找出引用在哪里保存。 其次,向您的Web应用程序添加一个关闭挂钩,以在取消部署应用程序时删除引用。 您可以通过使用Servlet上下文侦听器或通过第三方库提供的API来实现。
找到那些泄漏的参考从未如此简单。 我们自己花费了无数的时间试图找出为什么某些应用程序每次重新部署都需要20MB的PermGen。 但是从1.1版开始,Plumbr将向您显示泄漏的原因,并提示您如何修复它。 如果您想尝试一下,请注册并下载该工具 。 如果您运行的是Plumbr的旧版本,我们强烈建议您下载升级程序 。
参考: 什么是PermGen泄漏? 由我们的JCG合作伙伴 Nikita Salnikov Tarnovski在Plumbr Blog博客上获得。
翻译自: https://www.javacodegeeks.com/2012/12/what-is-a-permgen-leak.html