Apr 172005
 

ClassLoaders are interesting and I’ve been playing around with them a lot lately and definitely needed to jot a few things down so that I could not only remember it all but also use if for future reference and allow everyone else to do the same.

ClassLoaders reference class definitions, not data. Class definitions are the actual code and reflection information about the Class and are completely separate from the data of a Class instance, which is stored on the heap. Class definitions are loaded by the ClassLoader and stored in a special heap. If a ClassLoader is ever loading a Class definition if first should delegate (and all the out-of-the-box ClassLoaders do this) to its parent. When its parent doesn’t find the Class definition, then ClassLoader is free to load it.

Okay, let’s say we have Class MyService and Class Worker. Class MyService imports Class Worker. We also have ClassLoader P and ClassLoader C. ClassLoader P is the parent of ClassLoader C (think C-hild and P-arent). In addition, the only way that this really makes any sense whatsoever is that the ClassLoader C is the the current Thread context ClassLoader. There are a few situations that could occur:

  1. P loads MyService and P loads Worker
  2. P loads MyService and C loads Worker
  3. C loads MyService and P loads Worker
  4. C loads MyService and C loads Worker

Situation 1 means that the parent ClassLoader loads everything. The child ClassLoader doesn’t need to do anything. Situation 2 means that the parent ClassLoader loads MyService and when it tries to also load Worker it can’t. Since it is the parent, loading stops, but doesn’t fail because P can’t ask “down” to C for Worker. Situation 3 means that C loads MyService and when it tries to also load Worker, it first asks its parent for Worker, which its parent has. Situation 4 means that C loads both MyService and Worker. The parent is consulted twice, but has neither class.

Situation 2 is the sticky point here. Since the Sun JVM doesn’t fail during loading of MyService, it is tough to know what is actually going on when things start failing. Let’s elaborate on 2 for a second. Supposed MyService and Worker look like this:

In this case and with the ClassLoader situation from 2, when MyService's execute method is called, a ClassNotFoundException will occur when Worker is being instantiated. Even though C is able to load Worker AND it is the current context ClassLoader, it doesn’t matter! The golden rule of ClassLoaders, that always applies is that Classes from one ClassLoader cannot see Classes from child or sibling ClassLoaders. Since MyService is loaded by ClassLoader P, whenever it attempts to load other Classes it always starts with the ClassLoader it was loaded from. In this case, MyService will ask P for Worker. Since P doesn’t know about Worker, a ClassNotFoundException will be thrown.

Additionally, what this implies is that Class references are not bound at load time but rather delayed until they are referenced by the code that is executing. Likewise, Class definitions are bound to a ClassLoader and are constrained by that ClassLoader to only find Classes that the ClassLoader can resolve.

Must developers often refer to the fully-qualified name of Classes like:

This is only paritally true. When most people discuss ClassLoaders and Classes in articles and papers they refer to the the Classes fully-qualified name like this:

This includes the name of the Class, the package the Class is defined in and the ClassLoader that loaded it. Therefore, when Class definitions are loaded into the JVM and stored onto the special Class definition heap, they are defined using these three pieces of information.

This situation is difficult to remedy. This tough situation becomes even more difficult when MyService looks like this:

And MyService has a client that looks like this:

In this situtation, the Thread that is executing is also the Thread whose context ClassLoader has been set and it is also the service itself. Since the Thread was loaded by P, it makes little difference that I’ve changed the context ClassLoader to C. This code will still fail. So, here’s how I solved this problem.

First I realized that fully-qualified Class definitions don’t mean that the invocation of the class itself isn’t dynamic at runtime. The reason being that instances of Classes loaded by a ClassLoader can be passed around freely to instances of Classes loaded into any other ClassLoader. This is allowed only when the Class extends another Class or implements an Interface available in the other ClassLoaders (specifically because of situation 2 from above). Java holds instances in a shared pool that can be shared across Threads and have little concept of the ClassLoader that loaded them (except a reference to it and as part of its Class definition). In order to fully understand this, we need more examples. Let’s assume one more Class, AbstractWorker, from which Worker extends.

We also have to change MyService and the client so that they uses AbstractWorker.

Now, if we walk back over situation 2 from above, we can see what happens. First C loads Class Worker. Next, MyService is loaded by P, and passed Worker. During this instantiation and while the constructor is executing, MyService needs to load up AbstractWorker (recall that the Sun JVM performs lazy loading of Class definitions). Here, since MyService was loaded by P, when AbstractWorker needs to be loaded, it asks P to load it. Here we have two more situations:

  1. 2.0 P loads AbstractWorker
  2. 2.1 C loads AbstractWorker

In situation 2.0, P loads AbstractWorker, which makes MyService fully functional because it has all the Classes it needs. In situation 2.1, P still cannot load AbstractWorker and we are stuck in the same situation where P will always error out when it attempts to use AbstractWorker. In this new situation the error will occur when the MyService is initialized. The important point is that situation 2.0 works fine when P attempts to load the AbstractWorker Class definition.

Now what happens when the Client starts the MyService thread is that the instance of Worker is created by loading the Class definition for Worker using C. Worker extends AbstractWorker, which is all that MyService knows about. When MyService is handed a pointer to Worker it uses it as an AbstractWorker, but the Class that is running is the Worker instance. This is the crux of ClassLoader usage. Since Worker was already loaded, it doesn’t matter that it was loaded by another ClassLoader, MyService is still free to makes calls to Worker via its super-class AbstractWorker.

This illustrates that instances of Objects aren’t ClassLoader bound, while the Class deinitions are. Instances are free to roam about the JVM across Thread boundaries and to Classes loaded by any ClassLoader as long as the Class definitions of the Object instances are either known by the ClassLoader in question (situation 1, 3 and 4) or known via a super-class or interface (situation 2.0). In all other cases, the ClassLoader does not have enough information at at runtime to completely setup the Class definition and therefore will failed at runtime, usually with a ClassNotFoundException.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">