Pure Danger Tech


navigation
home

A Tale of Two Classloaders

15 Jun 2007

I’ve written before about using classloaders to isolate portions of your code. Lately, I’ve been in the thick of fighting some classloader issues so I thought I would add a few additional thoughts.

Post-delegation resource loading

One thing I did not mention in my previous post on writing a post-delegation classloader is that it is also essential to implement post-delegation resource loading. Every time I write one of these classloaders, I forget to do that and I end up debugging some nasty issue in a third party library (like a JDBC driver, app server lib, etc) at a most inconvenient time (like right before a release). So, you’ll want to add a method like this to your post-delegation classloader: [source:java]

public URL getResource(String name) {

// Try to find it locally first

URL url = findResource(name);</p>

// If not, try parent

if(url == null) {

url = super.findResource(name);

}

return url;

}

[/source]

Simple, but essential. Note to future me: don’t forget to do this next time!!

Linkage errors

Inevitably, if you do enough classloader work, you’ll hit the dreaded LinkageError. A LinkageError indicates a violation in the loading constraints. When using a post-delegation classloader, you will typically see LinkageErrors when both the main classloader and the plugin classloader load the same class AND the plugin passes that class back through the plugin interface. This will generally result in LinkageError and typically a stream of profanity (maybe that’s just me).

When using post-delegation, you need to ensure that any class that can be passed through your plugin interface (including exceptions!!) is defined only in your main classloader. You cannot define them in your plugin classloader as they will be loaded from there first due to post-delegation.

If you are using normal parent delegation, it’s perfectly ok to define the same class in both classloaders. Due to parent delegation, the main classloader’s version is always the one that will be used, even in the plugin’s domain.

Thread context classloader

Another tricky thing the first time you hit it is the thread context classloader. You can get and set the context classloader on java.lang.Thread via the getContextClassLoader() and setContextClassLoader() methods. When a Thread is created, it’s context classloader is set from the parent Thread. So, in the simplest case, most Threads end up with the system classloader as their context classloader.

The thread context classloader provides a way to pass an “intended classloader parent” into a framework without explicitly needing to pass it. However, Java frameworks do not follow consistent patterns for classloading. Some use the thread context classloader, some use the current classloader (the one that defined the current class), some accept an explicit ClassLoader argument, and some use a mixture of these approaches (with varying orderings).

The key thing to know though is that many common and important frameworks DO use the thread context classloader (JMX, JAXP, JNDI, etc). If you are using a J2EE application server, you are almost certainly relying on code using the thread context classloader.

So, if you have a plugin in a classloader (regardless of whether you are using post-delegation), it is a really good idea to wrap calls to the plugin interface and set the thread context classloader. Also important is that you need to remember the original context classloader and re-set it when you return from the plugin call. Otherwise, you have possibly installed a time bomb in the current thread. If the same thread later calls some other code that relies on the context classloader and that code is not written to properly set the context classloader, then that code will fail. This is particularly bad because it could occur at a much later point, far removed from the original failure location.

The easiest way to handle this problem is to use a dynamic proxy. This allows you to wrap all calls to a plugin interface with a single generic dynamic proxy. So, you might want to use something like this: [source:java]

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

public class ClassLoaderInjectorHandler implements InvocationHandler {

private final Object proxyInstance;

private final ClassLoader injectedClassLoader;

public ClassLoaderInjectorHandler(Object instance, ClassLoader classLoader) {

proxyInstance = instance;

injectedClassLoader = classLoader;

}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// Remember original thread context classloader

ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();

try {

// Set with the injected classloader

Thread.currentThread().setContextClassLoader(injectedClassLoader);

// Invoke the method on the proxy instance

return method.invoke(proxyInstance, args);

} finally {

// Replace the original classloader on the way out

Thread.currentThread().setContextClassLoader(originalLoader);

}

}

public static Object wrapClassLoaderInjector(Object instance, ClassLoader classLoader, Class<?>[] interfaces) {

InvocationHandler handler = new ClassLoaderInjectorHandler(instance, classLoader);

return Proxy.newProxyInstance(classLoader, interfaces, handler);

}

}

[/source]

The static method at the end is a helper method that can wrap any interface with a dynamic proxy that will inject the thread context classloader on all methods on an interface and replace the context classloader on the way out with a call like this (exception handling omitted): [source:java]

// Start with class name and a classloader

String className = “ExamplePluginImplementation”;

ClassLoader classLoader = …construct plugin classloader…

// Instantiate the plugin in the classloader

Class<?> pluginClass = classLoader.loadClass(className);

Plugin plugin = pluginClass.newInstance();

// Wrap the plugin instance in the dynamic proxy

plugin = (Plugin) ClassLoaderInjectorHandler.wrapClassLoaderInjector(

plugin, classLoader,

new Class<?>[] { Plugin.class } );

[/source]

I’ve found that not setting the thread context classloader can result in some error situations that can be maddening to track down. A great technique for doing so is to modify your classloader loadClass() method to print each class that is being loaded and whether or not it succeeded.

Generally, when you see something funky, there was a class loaded (or not loaded) in the region right before. It’s also extremely helpful to run in both a working and failing classloader situation (for example putting all your jars on the main classloader), and then diffing the output.

For instance, I tracked down a problem like this today – I was seeing a JMX error due to a missing protocol. In the working version of my environment, I could see the protocol class getting loaded and not getting loaded in the failing version. I took a look at the JMXConnectionFactory and saw that you can either set the context classloader or set a property when passing in the Context. I applied my wrapper dynamic proxy and things started working.