`

Classloader leaks: the dreaded "java.lang.OutOfMemoryError: PermGen space" excep

阅读更多
原文
http://blogs.sun.com/fkieviet/entry/classloader_leaks_the_dreaded_java
Classloader leaks: the dreaded "java.lang.OutOfMemoryError: PermGen space" exception

Did you ever encounter a java.lang.OutOfMemoryError: PermGen space error when you redeployed your application to an application server? Did you curse the application server, while restarting the application server, to continue with your work thinking that this is clearly a bug in the application server. Those application server developers should get their act together, shouldn't they? Well, perhaps. But perhaps it's really  your fault!

Take a look at the following example of an innocent looking servlet.

package com.stc.test;

import java.io.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class MyServlet extends HttpServlet {
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 // Log at a custom level
 Level customLevel = new Level("OOPS", 555) {};
 Logger.getLogger("test").log(customLevel, "doGet() called");
 }
}


Try to redeploy this little sample a number of times.  I bet this will eventually fail with the dreaded java.lang.OutOfMemoryError: PermGen space error. If you like to understand what's happening, read on.

The problem in a nutshell
Application servers such as Glassfish allow you to write an application (.ear, .war, etc) and deploy this application with other applications on this application server. Should you feel the need to make a change to your application, you can simply make the change in your source code, compile the source, and redeploy the application without affecting the other still running applications in the application server: you don't need to restart the application server. This mechanism works fine on Glassfish and other application servers (e.g. Java CAPS Integration Server).


The way that this works is that each application is loaded using its own classloader. Simply put, a classloader is a special class that loads .class files from jar files. When you undeploy the application, the classloader is discarded and it and all the classes that it loaded, should be garbage collected sooner or later.


Somehow, something may hold on to the classloader however, and prevent it from being garbage collected. And that's what's causing the java.lang.OutOfMemoryError: PermGen space exception.


PermGen space

What is PermGen space anyways? The memory in the Virtual Machine is divided into a number of regions. One of these regions is PermGen. It's an area of memory that is used to (among other things) load class files. The size of this memory region is fixed, i.e. it does not change when the VM is running. You can specify the size of this region with a commandline switch: -XX:MaxPermSize . The default is 64 Mb on the Sun VMs.


If there's a problem with garbage collecting classes and if you keep loading new classes, the VM will run out of space in that memory region, even if there's plenty of memory available on the heap. Setting the -Xmx parameter will not help: this parameter only specifies the size of the total heap and does not affect the size of the PermGen region.


Garbage collecting and classloaders
When you write something silly like


private void x1() {
        for (;;) {
            List c = new ArrayList();
        }
    }

you're continuously allocating objects; yet the program doesn't run out of memory: the objects that you create are garbage collected thereby freeing up space so that you can allocate another object. An object can only be garbage collected if the object is "unreachable". What this means is that there is no way to access the object from anywhere in the program. If nobody can access the object, there's no point in keeping the object, so it gets garbage collected. Let's take a look at the memory picture of the servlet example. First, let's even further simplify this example:

package com.stc.test;

import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Servlet1 extends HttpServlet {
private static final String STATICNAME = "Simple";
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
After loading the above servlet, the following objects are in memory (ofcourse limited to the relevant ones):

In this picture you see the objects loaded by the application classloader in yellow, and the rest in green. You see a simplified container object that holds references to the application classloader that was created just for this application, and to the servlet instance so that the container can invoke the doGet() method on it when a web request comes in. Note that the STATICNAME object is owned by the class object. Other important things to notice:


Like each object, the Servlet1 instance holds a reference to its class (Servlet1.class).
Each class object (e.g. Servlet1.class) holds a reference to the classloader that loaded it.
Each classloader holds references to all the classes that it loaded.
The important consequence of this is that whenever an object outside of AppClassloader holds a reference to an object loaded by AppClassloader, none of the classes can be garbage collected.

To illustrate this, let's see what happens when the application gets undeployed: the Container object nullifies its references to the Servlet1 instance and to the AppClassloader object.

As you can see, none of the objects are reachable, so they all can be garbage collected. Now let's see what happens when we use the original example where we use the Level class: package com.stc.test;

import java.io.*;
import java.net.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class LeakServlet extends HttpServlet {
 private static final String STATICNAME = "This leaks!";
 private static final Level CUSTOMLEVEL = new Level("test", 550) {}; // anon class!

 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 Logger.getLogger("test").log(CUSTOMLEVEL, "doGet called");
 }
}


Note that the CUSTOMLEVEL's class is an anonymous class. That is necessary because the constructor of Level is protected. Let's take a look at the memory picture of this scenario:

In this picture you see something you may not have expected: the Level class holds a static member to all Level objects that were created. Here's the constructor of the Level class in the JDK:

protected Level(String name, int value) {
 this.name = name;
 this.value = value;
 synchronized (Level.class) {
 known.add(this);
 }
 }


Here known is a static ArrayList in the Level class. Now what happens if the application is undeployed?


Only the LeakServlet object can be garbage collected. Because of the reference to the CUSTOMLEVEL object from outside of AppClassloader, the  CUSTOMLEVEL anyonymous class objects (LeakServlet$1.class) cannot be garbage collected, and through that neither can the AppClassloader, and hence none of the classes that the AppClassloader loaded can be garbage collected.

Conclusion: any reference from outside the application to an object in the application of which the class is loaded by the application's classloader will cause a classloader leak.


More sneaky problems

I don't blame you if you didn't see the problem with the Level class: it's sneaky. Last year we had some undeployment problems in our application server. My team, in particular Edward Chou, spent some time to track them all down. Next to the problem with Level, here are some other problems Edward and I encountered. For instance, if you happen to use some of the Apache Commons BeanHelper's code: there's a static cache in that code that refers to Method objects. The Method object holds a reference to the class the Method points to. Not a problem if the Apache Commons code is loaded in your application's classloader. However, you do have a problem if this code is also present in the classpath of the application server because those classes take precedence. As a result now you have references to classes in your application from the application server's classloader... a classloader leak!


I did not mentiond yet the simplest recipe for disaster: a thread started by the application while the thread does not exit after the application is undeployed.


Detection and solution
Classloader leaks are difficult. Detecting if there's such a leak without having to deploy/undeploy a large number of times is difficult. Finding the source of a classloader leak is even trickier. This is because all the profilers that we tried at the time, did not follow links through classloaders. Therefore we resorted to writing some custom code to find the leaks from memory dump files. Since that exercise, new tools came to market in JDK 6. The next blog will outline what the easiest approach today is for tracking down a glassloader leak.

分享到:
评论

相关推荐

    resolver_java_wsdl.jar

    at java.lang.ClassLoader.loadClassHelper(ClassLoader.java:688) at java.lang.ClassLoader.loadClass(ClassLoader.java:667) at com.ibm.ws.bootstrap.ExtClassLoader.loadClass(ExtClassLoader.java:119) at...

    如何解决java.lang.StackOverflowError

    在Java编程中,`java.lang.StackOverflowError` 是一个常见的运行时异常,它通常发生在程序执行过程中,当Java虚拟机(JVM)的调用栈溢出时。调用栈是每个线程用来存储方法调用信息的数据结构,当递归调用过深或者...

    (最终解决)java.lang.UnsatisfiedLinkError解决尝试

    2. 使用`java.lang.ClassLoader.loadLibrary()`代替`System.loadLibrary()`,这样可以提供更精确的库文件路径。 3. 检查是否有其他依赖库冲突,有时多个库可能使用相同的名字,导致加载混乱。 最后,博客链接<https...

    java.lang.ClassNotFoundException: org.jdom.input.SAXBuilder 处理

    当Java程序通过`Class.forName()`方法或者使用`ClassLoader`加载类时,如果在当前的类路径(Class Path)下找不到指定类的`.class`文件,就会抛出这个异常。这可能是由于以下原因: 1. **类路径设置不正确**:确保...

    hive-jdbc-1.1.0-cdh5.4.5-standalone.jar

    at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Unknown Source) at ...

    aop面向切面需要的jar包

    at java.lang.ClassLoader.loadClass(ClassLoader.java:307) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:252) at java.lang....

    swing-worker-1.1.jar

    Exceptionin thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/... at java.lang.ClassLoader.loadClass(ClassLoader.java:307) at java.lang.ClassLoader.loadClass(ClassLoader.java:248) ...9 more

    ClassLoader

    ### Java虚拟机中ClassLoader概述与双亲委托机制详解 #### 一、ClassLoader概念与作用 在Java编程语言中,`ClassLoader`是一个非常重要的组件,它负责加载程序运行所需的类文件到Java虚拟机(JVM)中。`ClassLoader`...

    java.lang包介绍

    Java编程语言的基础构建块之一是`java.lang`包,它被自动导入到每个Java程序中,无需显式导入。这个包包含了许多核心类和接口,是编写任何Java应用程序不可或缺的部分。`java.lang`包中最基本的类是`Object`,它是...

    hive-jdbc-2.1.0-standalone.jar

    at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Unknown Source) at ...

    jaxen.jar和dom4j.jar

    java.lang.... at java.lang.ClassLoader.loadClass(Unknown Source) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) ... 27 more

    Java.lang.reflect 包下常用的类及方法简介

    在Java编程语言中,`java.lang.reflect`包是核心库的一部分,它提供了运行时访问类、接口、字段和方法的能力。这个包对于理解和操作对象的动态特性至关重要,尤其是在实现反射机制时。反射允许我们在程序运行期间...

    android反编译dex2jar

    at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:276) at java.lang.ClassLoader.loadClass(ClassLoader.java:251) at java.lang....

    jdbc--sqljdbc4-2.0.jar

    at java.lang.ClassLoader.loadClass(Unknown Source) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.Class.forName0...

    java.lang.class源码-datanucleus-cache:修复java.lang.IncompatibleClassChange

    《深入解析Java.lang.Class源码:DataNucleus-Cache机制与修复IncompatibleClassChangeError》 在Java编程中,`java.lang.Class`是所有类的基石,它提供了访问类元数据、创建类实例以及执行类操作的关键方法。源码...

    北大青鸟Java.lang.rar经典例子

    在Java编程语言中,`java.lang`包是所有其他包的基础,它包含了Java程序中最基本、最核心的类。这个包中的类无需显式导入就能在任何Java源代码中使用,因为它们已经被自动导入到每个Java应用程序的上下文中。在这个...

    SpringMVC4.3.6配置json所需要的jar包

    SpringMVC4.3.6配置json所需要的jar包,不是使用最新最高的版本可以的,我尝试了,有错误:严重: Servlet.service() for servlet [springMVC] in context ... at java.lang.ClassLoader.defineClass1(Native Method)

    tomcat启动报错:java.util.zip.ZipException的解决方法

    在使用Apache Tomcat服务器时,有时会遇到启动异常的情况,其中一种常见的错误是`java.util.zip.ZipException`。这个异常通常表明在处理ZIP或JAR文件时遇到了问题,可能是因为文件损坏、格式不正确或者无法打开。在...

    ThrowingStream:java.util.stream的版本,允许引发检查的异常

    投掷流 该项目是java.util.stream及其各种支持接口的替代API...s.map(ClassLoader.getSystemClassLoader()::loadClass).forEach(System.out::println);输出: class java.lang.Objectinterface java.util.stream.Stream

Global site tag (gtag.js) - Google Analytics