atom feed2 messages in org.apache.tomcat.devProposition for a WebAppClassLoader e...
FromSent OnAttachments
Sylvain LaurentMar 31, 2009 2:45 pm.java
Sylvain LaurentApr 6, 2009 1:55 pm 
Subject:Proposition for a WebAppClassLoader enhancement : ExpendableClassLoader
From:Sylvain Laurent (sylv@m4x.org)
Date:Apr 6, 2009 1:55:54 pm
List:org.apache.tomcat.dev

[I'm re-sending this e-mail as I did not have any response. Maybe it was because of html format ?]

Hello,

Among the many possible causes of classloader leaks, one is the context class loader of threads spawned during the execution of web applications. For instance, if the following servlet is executed at least once and it is the first time the Graphics2D part of the JRE is used for the current JVM, then the web app's classloader will never be garbage- collected.

public class MyTomcatServlet extends HttpServlet {

@Override protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { System.out.println("MyServlet.doGet : current CCL:" + Thread.currentThread().getContextClassLoader()); BufferedImage image = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); response.setContentType("image/png"); OutputStream out = response.getOutputStream(); ImageIO.write(image, "png", out); out.close(); out.flush(); g.dispose(); } }

In order to work around such leaks, I created an "ExpendableClassLoader", which will leak instead of the normal WebAppClassLoader, But the former loads no class at all.

Here is the code :

/** * <p> * A special ClassLoader to work around classloader (and thus permgen) leaks * caused by never-ending threads spawned during the execution of the web app. * If no care is taken, such threads have the webapp's classloader as context * classloader. So, if such a thread is still alive after the application is * undeployed, the application's classloader cannot be garbage- collected. * </p> * <p> * When the application is undeployed, the reference to the delegated * classloader is nullified so that the latter can be garbage-collected * (provided it is not held by other means). * </p> * <p> * As of march 2009, there are several such "leaking" threads in Sun's JRE * library : Java2DDisposer {@link see
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6489540 }, LDAP connection pool manager... A very simple way * of provoking such a leak is with the following servlet code : * * <pre> * public class MyServlet extends HttpServlet { * * &#064;Override * protected void doGet(HttpServletRequest req, HttpServletResponse response) * throws ServletException, IOException { * BufferedImage image = new BufferedImage(20, 20, * BufferedImage.TYPE_INT_RGB); * Graphics2D g = image.createGraphics(); * response.setContentType(&quot;image/png&quot;); * OutputStream out = response.getOutputStream(); * ImageIO.write(image, &quot;png&quot;, out); * out.close(); * out.flush(); * g.dispose(); * } * } * </pre> * * </p> * <p> * By using this ExpendableClassLoader as the webapp's classloader (and thus the * context classloader), such dangling threads only keep a reference to a very * light classloader, drastically reducing the leak. * </p> * <p> * NOTE: the class has to be in the same package as {@link WebappClassLoader} in * order to override package-protected methods. * </p> * * @author Sylvain LAURENT * */ public class ExpendableClassLoader extends WebappClassLoader {

private WebappClassLoader delegatedClassLoader;

public ExpendableClassLoader(ClassLoader parentClassLoader) { delegatedClassLoader = new WebappClassLoader(parentClassLoader); }

public void stop() throws LifecycleException { ClassLoader savedParentClassLoader = delegatedClassLoader.getParent();

delegatedClassLoader.stop();

// release reference to the delegated classloader, potentially // sacrificing the current instance delegatedClassLoader = null;

// recreate a delegated classloader, just in case a dangling Thread // (e.g. JDK threads like Java2Disposer) needs to load a class // we use the same parent CL as the previous delegate delegatedClassLoader = new WebappClassLoader(savedParentClassLoader);

}

public String toString() { return "ExpendableClassLoader@" + System.identityHashCode(this) + " delegates to " + delegatedClassLoader.toString(); }

/* ------------------- delegated methods --------------------- */ //skipped for brevity }

I tested it with a small webapp and a custom context.xml to override the default WebAppClassLoader and it does its job of avoiding this type of memory leak.

I'd be glad to read your opinion on this hack !

Sylvain