[原创](翻译)什么是Java的永久代(PermGen)内存泄漏

本文是我对这篇文章的翻译:What is a PermGen leak? 为了便于阅读,我将原文附于此处,翻译穿插在其中。此外,为了防止原链接在未来某一天失效后,文中的图片再也看不到的问题,我将原文中的图片也保存到了本站的服务器上,我不知道原作者是否允许这样做,但我翻译本文仅在于传播知识的目的,在此向原作者表示深深的感谢:感谢你们的分享。

WHAT IS A PERMGEN LEAK?
什么是Java的永久代(PermGen)内存泄漏?

What follows is a practical introduction to a specific type of memory problems in Java applications. Namely – we will analyze the errors that cause the java.lang.OutOfMemoryError: PermGen spacesymptom in the stack trace.
本文实用性地介绍了Java程序的一个特殊的内存问题,即——我们将分析stack trace中产生java.lang.OutOfMemoryError: PermGen space错误的原因。

First of all we will go through the core concepts required to understand the subject – and explain what objects, classes, classloaders and the JVM memory model are. If you are familiar with the basic concepts, you can jump directly to the next section where I will describe two typical cases for the error in question alongside with hints and suggestions for solving it.
首先,我们来看看理解该主题所需的核心概念,并且解释一下什么是对象(objects),类(classes),类加载器(classloaders)以及JVM内存模型(JVM memory model)。如果你已经熟知了这些基础概念,可以直接跳到下一节,我在它里面讨论了两种产生该错误的典型原因,以及解决此问题的提示和建议。
文章来源:http://www.codelast.com/

Objects, Classes and ClassLoaders

对象(Objects),类(Classes)以及类加载器(ClassLoaders)

Well, I will not start with the very basics. I guess if you have already found us, you should be familiar with the concept that everything in Java is an Object. And that all Objects are specified by their Class. So every object has a reference to an instance of java.lang.Class describing the structure of that object’s class.

But what actually happens under the hood, when you create a new object in your code? For example if you write something truly complicated like

我不会从最基础的概念开始讲。我假设你已经知道了我们,你应该熟知:在Java中一切皆是对象(Object),并且所有对象都是由它们的类(Class)指定的。所以每一个对象都有一个到java.lang.Class(用于描述对象的结构)的实例的引用。
但是当你在代码中创建一个新对象的时候,到底发生了什么呢?例如,当你写下如下代码的时候:

 

Person boss = new Person();

The Java Virtual Machine (JVM) needs to understand the structure of the object to create. To achieve this, the JVM looks for the class called Person. And if the Person class is accessed for the first time during this particular execution of the program, it has to be loaded by the JVM, normally from the corresponding Person.class file. The process of seeking for the Person.class file on the drive, loading it into memory and parsing it’s structure is called class loading. Ensuring proper class loading process is the responsibility of a ClassLoaderClassLoaders are instances of java.lang.ClassLoader class and each and every class in a Java program has to be loaded by some ClassLoader. As a result we now have the following relationships:
Java虚拟机(JVM)需要理解待创建对象的结构,为此,JVM会查找叫Person的类。并且,如果在程序的这一次运行中,Person类是第一次被访问的话,它必须要被JVM加载(loaded),通常这是从对应的Person.class文件加载的。从磁盘上查找Person.class文件、将其加载到内存中,并解析它的结构的过程,就叫作类的加载(class loading)。保证合适的类被加载是类加载器(ClassLoader)的职责。ClassLoader是java.lang.ClassLoader类的实例,并且Java程序中的每一个类都必须被一些ClassLoader加载。最后,我们可以得到下面的关系图:

文章来源:http://www.codelast.com/
As you can see from the next diagram every classloader holds references to all the classes it has loaded. For the purpose of our article these relationships are very interesting.
从下一幅图你可以看到,每一个classloader都持有它所加载的所有类的引用。由于本文的主旨的原因,这些关系非常有趣。

Remember this image, we will need it later.
记住这幅图,我们稍后会用到它。
文章来源:http://www.codelast.com/

Permanent Generation

永久代

Almost every JVM nowadays uses a separate region of memory, called the Permanent Generation (orPermGen for short), to hold internal representations of java classes. PermGen is also used to store more information – find out the details from this post if you are interested – but for our article it is safe to assume that only the class definitions are being stored in PermGen. The default size of this region on my two machines running java 1.6 is not a very impressive 82MB.
当今,几乎所有JVM都使用一个称为永久代(Permanent Generation,或者简写为PermGen)的独立内存区域来保存Java类的内在表征。PermGen也用来保存更多信息——如果感兴趣的话,你可以看看这篇文章——但是对本文来说,我们可以假定PermGen只保存类的定义。对我那运行Java 1.6的机器来说,此内存区域的默认大小是82MB,一个很普通的数值。


 

As I have explained in one of my earlier posts, a memory leak in Java is a situation where some objects are no longer used by an application, but the Garbage Collector fails to recognize them as unused. This leads to the OutOfMemoryError if those unused objects contribute to the heap usage significantly enough that the next memory allocation request by the application cannot be fulfilled.
就像我在以前一篇文章中解释的那样,Java的内存泄漏是指某些对象不再被应用程序使用,而垃圾收集器(Garbage Collector)却没能识别它们是“不再使用的”。如果那些不使用的对象占用堆(heap)空间足够大,使得应用程序无法满足下一次内存分配需求,就会导致OutOfMemoryError错误。

The root cause of java.lang.OutOfMemoryError: PermGen space is exactly the same: the JVM needs to load the definition of a new class but there is not enough space in PermGen to do it – there are already too many classes stored there. A possible reason for this could be your application or server using too many classes for the current size of PermGen not to be able to accommodate them. Another commone reason could be a memory leak.
java.lang.OutOfMemoryError: PermGen space错误的根本原因也是完全一样的:JVM需要加载一个新类的定义,而永久代(PermGen)的空间不足——已经有太多的类存储在那里了。一个可能的原因是:你的应用程序或服务器使用了太多的类,当前的永久代(PermGen)大小无法满足需求。另一个原因可能是内存泄漏。
文章来源:http://www.codelast.com/

Permanent Generation Leak

永久代(内存)泄漏

But still, how on earth it is possible to leak something in PermGen? It holds definitions of java classes and they cannot become unused, can they? Actually, they can. In case of a Java web application deployed into an application server all those classes in your EAR/WAR become worthless when the application is undeployed. The JVM continues to run as the application server is still alive, but a whole bunch of class definitions are not in use anymore. And they should be removed from PermGen. If not, we will have memory leak in the PermGen area.
但是,永久代(PermGen)到底有没有可能会内存泄漏?它保存了Java类定义,并且这些类定义是不会变成“无用”的,是吗?事实上,它们是可以变成“无用”的。以一个部署到应用程序服务器的Java web程序来说,当该应用程序被卸载的时候,你的EAR/WAR包中的所有类都将变得无用。只要应用程序服务器还活着,JVM将继续运行,但是一大堆的类定义将不再使用,理应将它们从永久代(PermGen)中移除。如果不移除的话,我们在永久代(PermGen)区域就会有内存泄漏。

As a nice sample on the reasons – the Tomcat developers have set up a Wiki page describing different leaks found and fixed in the Apache Tomcat versions 6.0.24 and above.
这里有一个说明内存泄漏的原因的好例子——Tomcat的开发者们写了一个Wiki页面来描述在Apache Tomcat 6.0.24及之后的版本中发现、修复的不同的内存泄漏原因。
文章来源:http://www.codelast.com/

Leaking Threads

线程内存泄漏

One possible scenario for a classloader leak is through long running threads. This happens when your application or – as often was the case in my experience – a 3rd party library used by your application, starts some long running thread. An example of this could be a timer thread whose job is to execute some code periodically.
类加载器(classloader)泄漏的一个可能的场景就是通过运行的线程(而内存泄漏)。当你的程序,或者你的程序使用的第三方库(我经常遇到这种情况)开启了一些长时间运行的线程。一个例子:一个用于周期性执行代码的计时器(timer)线程。

If the intended lifespan of this thread is not fixed, we are heading directly into a trouble. When any part of your application ever starts a thread, you must make sure that it is not going to outlive the application. In typical cases the developer either is not aware of this responsibility or simply forgets to write the clean-up code.
如果不解决该线程预期的生命周期问题,我们直接会遇到麻烦。当你程序的任何一部分启动一个线程的时候,你要确保它不会比程序活得还要久。在典型的情况下,开发者要么不知道自己有责任处理好这个问题,或者忘了写清理(clean-up)的代码。

Otherwise, if some thread continues to run after the application is undeployed, it will, usually, hold a reference to a classloader of the web application it was started by, called context classloader. Which in turn means that all classes of the undeployed application continue to be held in memory. Remedy? If it is your application that starts new threads, you should shut them down during undeployment using a servlet context listener. If it is a 3rd party library, you should search for its own specific shutdown hook. Or file a bug report if there is none.
否则,如果应用程序卸载后,线程还在继续运行,它通常将维持一个到web应用程序的classloader的引用,即我们所说的context classloader。这也就意味着,所有卸载掉的应用程序仍然保存在内存中。怎么解决?如果是你的程序开启了新线程,那么你就应该在卸载的时候关闭它们,这可以通过使用一个servlet context listener来实现。如果是第三方库开启的新线程,你应该搜索它的关闭线程的接口,如果没有的话,就上报一个bug吧。
文章来源:http://www.codelast.com/

Leaking Drivers

驱动内存泄漏

Another typical case of a leak can be caused by database drivers. We have encountered this leak in our own demo application that we ship with Plumbr. It is a slightly modified Pet Clinic application shipped along with Spring MVC. Let us highlight some things that happen when this application is being deployed to the server.
另一个典型的内存泄漏原因是由数据库驱动造成的。我们在和Plumbr一起发布的demo程序中遇到了这种内存泄漏情况。它是一个与Sprint MVC一起发布的、代码稍微修改过的Pet Clinic程序。让我们关注一下当这个应用程序部署到服务器上的时候,发生了什么:

  • The server creates a new instance of java.lang.Classloader and starts to load the application’s classes using it.
  • 服务器创建一个java.lang.Classloader的新实例,并用它来加载程序的类。
  • Since the PetClinic uses a HSQL database, it loads the corresponding JDBC driver,org.hsqldb.jdbcDriver
  • 由于PetClinic使用了HSQL数据库,所以它会加载相应的JDBC驱动,即org.hsqldb.jdbcDriver
  • This class, being a good-mannered JDBC driver, registers itself with java.sql.DriverManager during initialization, as required per JDBC specification. This registration includes storing inside a static field of DriverManager a reference to an instance of org.hsqldb.jdbcDriver.
  • 这个JDBC驱动类会在初始化的时候将它注册到java.sql.DriverManager中(正如JDBC规范所要求的那样)。这个注册过程包括了存储org.hsqldb.jdbcDriver的一个实例的引用到DriverManager的一个静态域中

Now, when the application is undeployed from the application server, the java.sql.DriverManager will still hold that reference, as there is no code in the HSQLDB library nor in the Spring framework nor in the application to remove that! As was explained above, a jdbcDriver object still holds a reference to anorg.hsqldb.jdbcDriver class, which in turn holds a reference to the instance of java.lang.Classloaderused to load the application. This classloader now still reference all the classes of the application. In case of our particular demo application, during application startup almost 2000 classes are loaded, occupying roughly 10MB in PermGen. Which means that it takes about 5-10 redeploys to fill the PermGen with default size to reach the java.lang.OutOfMemoryError: PermGen space crash.
现在,当从服务器上卸载应用程序的时候,java.sql.DriverManager仍将持有那个引用,无论在HSQLDB库,或者在Spring framework中,都没有代码可以移除它!正如上面解释的那样,一个jdbcDriver对象将持有一个到org.hsqldb.jdbcDriver类的引用,从而持有用于加载应用程序的java.lang.Classloader的一个实例的引用。这个classloader现在仍然引用着应用程序的所有类。在我们那特殊的demo应用程序中,在程序启动的时候,需要加载将近2000个类,占用约10MB永久代(PermGen)内存。这就意味着需要5~10次重新部署,才会将默认大小的永久代(PermGen)塞满,然后就会触发java.lang.OutOfMemoryError: PermGen space错误并崩溃。

How to fix that? One possibility is to write a servlet context listener, which de-registers the HSQLDB driver from DriverManager during application shutdown. This is pretty straightforward. But remember – you will have to write the corresponding code in every application using the driver.
怎样解决此问题?一个可能的办法就是写一个servlet content listener,用于在应用程序关闭的时候,从DriverManager反注册HSQLDB驱动。这个方法很直接,但是请记住——你需要在使用该驱动的每一个应用程序中都这么写。

Download our latest version of Plumbr with our demo application and play with it to find out how the leak occurs, how Plumbr finds it and how do we explain the cause.
下载我们最新版的Plumbr以及demo应用程序来研究一下,看看内存泄漏是怎么发生的,Plumbr是怎么发现它,以及我们是怎么解释其原因的。
文章来源:http://www.codelast.com/

Conclusion

结论

There are many reasons why your application might encounter a java.lang.OutOfMemoryError: PermGen space. The root cause for the majority of them is some reference to an object or a class loaded by the application’s class loader that has died after that. Or a direct link to the class loader itself. The actions you need to take for remedy are quite similar for most of these causes. Firstly, find out where that reference is being held. Secondly, add a shutdown hook to your web application to remove the reference during application’s undeployment. You can do that by either using a servlet context listener or by using the API provided by your 3rd party library.
你的应用程序遇到java.lang.OutOfMemoryError: PermGen space错误的原因很多,究其根本原因,大多数是由于object或程序的class loader加载的类的引用已经无用了导致的。对此类问题,你需要采取的补救措施都非常相似,即,首先,找出引用在哪里被持有;其次,给你的web应用程序添加一个关闭的hook,或者在应用程序卸载后移除引用。你要么通过servlet context listener,要么通过第三方库提供的API来实现这一点。

Finding those leaking references has never been easy. We ourselves have spent countless hours trying to trace down why some applications require 20MB of PermGen on each redeploy. But as of version 1.1,, Plumbr will show you the cause of the leak and give you a hint on how to fix it. If you want to try it out, register and download the tool. If you are running an older version of Plumbr, we strongly recommend downloading an upgrade.
找出内存泄漏的引用从来就不是件容易事。例如,就我们自己来说,我们曾经花费了数不清的时间去追踪为什么某些程序每一次部署都需要用掉20MB的永久代(PermGen)内存。但是,Plumbr 1.1版能告诉你内存泄漏的原因,并给出修复提示。如果你想试试,就去注册下载吧。如果你还在使用旧版的Plumbr,我们强烈建议你升级到新版。

文章来源:https://www.codelast.com/
➤➤ 版权声明 ➤➤ 
转载需注明出处:codelast.com 
感谢关注我的微信公众号(微信扫一扫):

wechat qrcode of codelast

发表评论