Skip to content.

Manageability

Sections
Personal tools
You are here: Home » blog » stuff in progress » Class Loading

Class Loading

ClassLoader's are tricky... here's a few tips. Boy I tell you, in working on my own plugin engine with dynamically reloadable plugins, I have learned quite a bit about classloaders, how the JVM loads classes, how it defines classes, how to delegate loading from one classloader to another to avoid multiple copies of the same class loaded, and so forth.

A lot of developers have worked with classloaders to an extent, but from what I have seen in articles and forum posts, most of these developers use the Java 2 delegation model to build custom loaders. The Java 2 delegation means you extend a classloader, usually URLClassLoader, and implement the findClass() method, so that the parent loader chain is ALWAYS checked before your findClass() gets a chance to look for a class. This is great if you have no need to unload/reload classes, or really don't mind what parent loader in the chain may actually load a class. But if you want a bit more control over the process, you have to dig a little deeper. Instead of findClass(), you'll want to override the loadClass() method, which is the method the JVM calls first to load a class. You can follow the same principles as URLClassLoader and other Java 2 delegation folowing loaders do. You ask the parent to load your class first, if it's not found, you then provide your own code in the loadClass method. How is this different? Not much if you do it that way. However, suppose you don't want the parent loader to load your class. There are numerous reasons why, from blocking certain java API's (for security purposes possibly), preventing the app loader from loading the class so that you might unload the class (by discarding the classloader... so long as no other class has a ref to any instances of the class to be unloaded), or simply to "filter" what classes can be allowed to be used, thus providing your own sort of "private/public" access to specific classes possibly based on user authentication. The trick here is to not call the parent loader first. You also may actually have to provide a try..catch block to avoid a ClassNotFoundException being thrown out of the loadclass method before you get to fully try to find it via your code. Below is a simple "custom" loader that tries to find the class within the classpath of the classloader first, then, if a flag is set, uses the parent loader if still not found:

public class CustomClassLoader extends URLClassLoader { private boolean useParent = true;

public CustomClassLoader(URL[] urls) { this(urls, null); }

public CustomClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); }

public synchronized Class loadClass(String className) throws ClassNotFoundException { // First, let's ignore any java. and javax. classes by telling the parent to load them. if (className.indexOf("java.") > 0 || className.indexOf("javax.") > 0) { return getSystemLoader().loadClass(className); }

// Try our built-in cached classes, if its already loaded Class clazz = findLoadedClass(className);

if (null == clazz) { // didn't find it in cache, so now we'll try the // classpath of this loader, using its URL[]'s to // look in. This is implemented in the super // URLClassLoader, so we get this for free. // Because this next step may not find the class, // it would throw a ClassNotFoundException. However, // we don't want to end looking for a class there, // so we'll catch this exception if it occurs, ignore // it and/or log it, and continue to the next step. try { clazz = findClass(className);

// if found, a ClassNotFoundException wasn't thrown, so we // can return it. return clazz; } catch (ClassNotFoundException cnfe) { // log or ignore it. } }

// Ok, not found in cache or classpath, so here we may want to // delegate the loading to any number of loaders. Perhaps a // loader is used to place commonly used libraries. If so, we may // want to try to find the class there first before giving up or // use the parent.

// At this point, class is not found, so we'll see if the flag is set // to look in the parent. If so, we'll finally ask the parent to load // the class. Because we want to throw a ClassNotFoundException if the // parent can't find it, we wont wrap this up in a try..catch block. if (useParent) { return getParent().loadClass(className); }

// If the useParent if false, we'll get here if it wasn't found anywhere // else, so we'll want to throw a ClassNotFoundException anyway. throw new ClassNotFoundException(className + " was not found."); } }

So, the above is basically a "template" loader that can be used to do all kinds of crazy loading schemes. The interesting aspect with this type of loader is that you can use it, using the parent loader flag set to true (default) just like a normal classloader and it will work fine. The first if() basically says, we don't want to try to load java. or javax. classes, they are found in rt.jar and loaded by the bootstrap classloader (the native implemented loader, the first loader, the only loader without a parent!). There is no need to waste time loading any java API classes. We could add com.sun.* and so forth, but they are far less often used than the normal java API classes. The benefit here, unlike most classloaders, is that we aren't going through the rest of the steps with looking for java. and javax. classes. We are simply delegating directly to the system loader which will find it right away, thus making the lookup of java api classes faster. After that, the cache is a built in feature of the Java 2 classloader mechanism. It used to be prior to 1.2 that you would have to maintain your own Map of classes that were already loaded. Because a class is stored via the class and its classloader, once a class is loaded by a classloader and stored in its cache, even if the class was loaded by another classloader (delegated to from this loader), the JVM basically stores a reference to the class:classloader. What is ironic about this is that classes loaded by one loader can not be shared with the exact same classes loaded by another loader, even if they are the same physical .class file on disk. Therefore, if you wish to try to avoid multiple .class bytecodes of the same class being loaded by different classloader instances (if your app gets sophisticated enough to warrant multiple instances..thus it would be a plugin engine or a j2ee or web container of some sort most likely), you would need to delegate from differnet loaders to the one loader that "defines" the bytecode of the .class file. By doing this, you save on resources, as well as performance by not having to load and define a .class file again. If you do not do this and try to pass class instanes of the same .class file but loaded by different loaders, you'll get ClassCastException errors because despite being the same exact .class file and bytecode, the JVM treats class1:classloader1 different than class1:classloader2.

So, have fun with the custom loader. Try cutting out the use of the parent loader, see what happens. It will enforce your classes to reside within the classpath of the classloader URL[] list, or not be found. Great for allowing the reloading of classes, or unloading.

Created by admin
Last modified 2004-09-16 05:30 PM

 

Powered by Plone

This site conforms to the following standards: