You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by Sylvain Laurent <sy...@m4x.org> on 2009/04/06 22:56:17 UTC

Proposition for a WebAppClassLoader enhancement : ExpendableClassLoader

[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

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org