12.1 A brief introduction to Java class loaders
Class loaders enable the Java virtual machine (JVM) to load classes. Given the name of a class, the class loader locates the definition of this class. Each Java class must be loaded by a class loader.
When you start a JVM, you use three class loaders: the bootstrap class loader, the extensions class loader, and the application class loader.
- The bootstrap class loader is responsible for loading only the core Java libraries, that is vm.jar, core.jar, and so on, in the <JAVA_HOME>/jre/lib directory. This class loader, which is part of the core JVM, is written in native code.
- The extensions class loader is responsible for loading the code in the extensions directories (<JAVA_HOME>/jre/lib/ext or any other directory specified by the java.ext.dirs system property). This class loader is implemented by the sun.misc.Launcher$ExtClassLoader class.
- The application class loader is responsible for loading the code that is found on java.class.path, which ultimately maps to the system CLASSPATH variable. This class loader is implemented by the sun.misc.Launcher$AppClassLoader class. The parent-delegation model is a key concept to understand when dealing with class loaders. It states that a class loader delegates class loading to its parent before trying to load the class itself. The parent class loader can be either another custom class loader or the bootstrap class loader. But what is very important is that a class loader can only delegate requests to its parent class loader, never to its child class loaders (it can go up the hierarchy but never down).
The extensions class loader is the parent for the application class loader. The bootstrap class loader is the parent for the extensions class loader. The class loaders hierarchy is shown in Figure 12-1.
If the application class loader needs to load a class, it first delegates to the extensions class loader, which, in turn, delegates to the bootstrap class loader. If the parent class loader cannot load the class, the child class loader tries to find the class in its own repository. In this manner, a class loader is only responsible for loading classes that its ancestors cannot load.
Figure 12-1 Java class loaders hierarchy
This behavior can lead to some interesting problems if a class is loaded from a class loader that is not on a leaf node in the class loader tree. Consider Example 12-1. A class called WhichClassLoader1 loads a class called WhichClassLoader2, in turn invoking a class called WhichClassLoader3.
Example 12-1 WhichClassLoader1 and WhichClassLoader2 source code
public class WhichClassLoader1 {
public static void main(String[] args) throws javax.naming.NamingException { // Get classpath values String bootClassPath = System.getProperty("sun.boot.class.path"); String extClassPath = System.getProperty("java.ext.dirs"); String appClassPath = System.getProperty("java.class.path");
// Print them out System.out.println("Bootstrap classpath =" + bootClassPath + "\n"); System.out.println("Extensions classpath =" + extClassPath + "\n"); System.out.println("Application classpath=" + appClassPath + "\n");
// Load classes Object obj = new Object(); WhichClassLoader1 wcl1 = new WhichClassLoader1(); WhichClassLoader2 wcl2 = new WhichClassLoader2();
// Who loaded what? System.out.println("Object was loaded by " + obj.getClass().getClassLoader()); System.out.println("WCL1 was loaded by " + wcl1.getClass().getClassLoader()); System.out.println("WCL2 was loaded by " + wcl2.getClass().getClassLoader());
wcl2.getTheClass(); } } ========================================================================== public class WhichClassLoader2 {
// This method is invoked from WhichClassLoader1 public void getTheClass() { WhichClassLoader3 wcl3 = new WhichClassLoader3(); System.out.println("WCL3 was loaded by " + wcl3.getClass().getClassLoader()); } }
If all WhichClassLoaderX classes are put on the application class path, the three classes are loaded by the application class loader, and this sample runs just fine.
Now suppose you package the WhichClassLoader2.class file in a JAR file that you store under <JAVA_HOME>/jre/lib/ext directory. You see the output in Example 12-2.
Example 12-2 NoClassDefFoundError exception trace
Bootstrap classpath =C:\WebSphere\AppServer\java\jre\lib\vm.jar;C:\WebSphere\AppServer\java\jre\lib\core.jar;C:\WebSphere\AppServer\java\jre\lib\charsets.jar;C:\WebSphere\AppServer\java\jre\lib\graphics.jar;C:\WebSphere\AppServer\java\jre\lib\security.jar;C:\WebSphere\AppServer\java\jre\lib\ibmpkcs.jar;C:\WebSphere\AppServer\java\jre\lib\ibmorb.jar;C:\WebSphere\AppServer\java\jre\lib\ibmcfw.jar;C:\WebSphere\AppServer\java\jre\lib\ibmorbapi.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjcefw.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjgssprovider.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjsseprovider2.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjaaslm.jar;C:\WebSphere\AppServer\java\jre\lib\ibmjaasactivelm.jar;C:\WebSphere\AppServer\java\jre\lib\ibmcertpathprovider.jar;C:\WebSphere\AppServer\java\jre\lib\server.jar;C:\WebSphere\AppServer\java\jre\lib\xml.jar Extensions classpath =C:\WebSphere\AppServer\java\jre\lib\ext Application classpath=.
Exception in thread "main" java.lang.NoClassDefFoundError: WhichClassLoader3 at java.lang.J9VMInternals.verifyImpl(Native Method) at java.lang.J9VMInternals.verify(J9VMInternals.java:59) at java.lang.J9VMInternals.initialize(J9VMInternals.java:120) at WhichClassLoader1.main(WhichClassLoader1.java:17)
As you can see, the program fails with a NoClassDefFoundError exception, which might sound strange because WhichClassLoader3 is on the application class path. The problem is that it is now on the wrong class path.
What happened was that the WhichClassLoader2 class was loaded by the extensions class loader. In fact, the application class loader delegated the load of the WhichClassLoader2 class to the extensions class loader, which in turn delegated the request to the bootstrap class loader. Because the bootstrap class loader could not find the class, the class loading control was returned to the extensions class loader. The extensions class loader found the class on its class path and loaded it.
Now, when a class has been loaded by a class loader, any new classes that the class needs reuse the same class loader to load them (or goes up the hierarchy according to the parent-delegation model). So when the WhichClassLoader2 class needed to access the WhichClassLoader3 class, it is the extensions class loader that first gets the request to load it. The extensions class loader first delegates the request to the Bootstrap class path, which cannot find the class, and then tries to load it itself but does not find it either because WhichClassLoader3 is not on the extensions class path but on the application classpath. And because the extensions class loader cannot delegate the request to the application class loader (a delegate request can only go up the hierarchy, never down), a NoClassDefFoundError exception is thrown.
Remember that developers very often also load property files through the class loader mechanism using the following syntax:
Properties p = new Properties(); p.load(MyClass.class.getClassLoader().getResourceAsStream("myApp.properties"));This means, if the class MyClass is loaded by the extensions class loader and the myApp.properties file is only seen by the application class loader, the loading of the property file fails.