.
In Brief
147
14
Controlling Class
Loading
“Nobody notices when things go well.”
Zimmerman’s Law of Complaints
Class loadersload and initialize classes and interfaces in the Java virtual machine (JVM). This chapter provides an overview of this process and shows you how to develop a custom class loader that allows decoration of loaded byte- code.
JVM Internals from a Class
Loading Perspective
The Java virtual machine running on top of the operating system (OS) provides a level of abstraction to the applica- tions written in Java. Language specification and standard- ized Application Programming Interfaces (APIs) enable cross-platform development and deployment because the applications do not make platform-specific calls directly to the OS. Most of the core Java APIs are themselves written in Java, with only a small portion of native code accessed via JNI. Java applications can contain bytecode and native dynamic libraries. The native libraries are distributed as dynamically linked libraries (.dll) for Windows and shared libraries (.so) for Unix. To execute a Java applica- tion, the JVM uses a class loader to load and initialize the system and application classes, which can result in loading of the application native libraries. The JVM from a class loading perspective is shown in Figure 14.1.
FIGURE 14.1 The JVM from a class loading perspective.
Although a typical application need not be concerned with the loading and initialization, the capability to control this process is exploited in techniques such as runtime bytecode instru- mentation and bytecode integrity protection.
The application loading process begins with the initial class that was provided to the Java launcher (typically a command-line parameter to the Java runtime). All parent classes and any classes referenced by the main()method of the initial class are lazily loaded and initial- ized as the references are made. Unless explicitly specified, the class loader of the referring class is used to load the referred class. If class loaders load Java classes, what is used to load class loaders, you might ask? The answer is the bootstrapclass loader, which is implemented in native code and is used to load Java core classes such as java.lang.Objectand
java.lang.ClassLoader. The extensionsclass loader is used to load the extension libraries typically from the lib/extdirectory of JRE, and the JAR files placed in that directory are automatically available to Java applications. The applicationclass loader is created internally by the JVM launcher to load classes from the standard CLASSPATH. Finally, a class loader called the systemclass loader is generally the same as the application class loader.
Class loaders are organized in a chain, in which a child lets the parent find the class before attempting to find it itself. Generally, a class loader first checks whether it has already loaded and initialized the class. If the class is not loaded yet, the class loader attempts to create or load it and, if found, initialize it in the JVM. The bootstrap class loader is the root of the class loaders hierarchy and corresponds to the value of null, whereas the system class loader is the default delegation parent for new class loaders. Table 14.1 lists the class loaders and the infor- mation about their sources of class data.
Application Classes
System Classes
System Native Code Application Native Code Application
Class File
System Class File
System Native Library File
Application Native Library File JNI
JNI
Class Loader
TA B L E 1 4 . 1
Class Loaders and Their CLASSFile Sources CLASS LOADER SOURCE OF CLASS DATA
Bootstrap Directories and JAR files listed in the system property sun.boot.class.path, which by default includes the core runtime classes in rt.jarand a few other standard JARs. It can be manipulated using the -Xbootclasspathcommand-line parameter to the Java launcher.
Extensions Directories listed in the system property java.ext.dirs, which by default points to the lib/extdirectory of the JRE. However, it can be explicitly specified at the command line using -Djava.ext.dirs=<path>.
Application Directories and JARs listed in the system property java.class.path, which by default is set from the CLASSPATHenvironment variable.
To peek at the hierarchy of class loaders at runtime, we will write a simple class that prints the class loader used to load it and the chain of its parents. The code for the class is shown in Listing 14.1.
LISTING 14.1 PrintClassLoadersSource Code
public class PrintClassLoaders {
public static void main(String[] args) {
System.out.println(“System class loader = “ + ClassLoader.getSystemClassLoader());
System.out.println(“Thread context class loader = “ + Thread.currentThread().getContextClassLoader());
System.out.println(“Class loader hierarchy for this class:”); String padding = “”; ClassLoader cl = PrintClassLoaders.class.getClassLoader(); while (cl != null) { System.out.println(padding + cl); cl = cl.getParent(); padding += “ “; }
System.out.println(padding + “null (bootstrap class loader)”); }
}
The PrintClassLoadersclass is located in the covertjava.classloaderpackage. Running the class produces the following output:
141
System class loader = sun.misc.Launcher$AppClassLoader@12f6684
Thread context class loader = sun.misc.Launcher$AppClassLoader@12f6684 Class loader hierarchy for this class:
sun.misc.Launcher$AppClassLoader@12f6684 sun.misc.Launcher$ExtClassLoader@f38798
null (bootstrap class loader)
We can conclude from the output that the class loaders hierarchy corresponds to the order in which the runtime attempts to find and load a class. The AppClassLoaderinstance returned by the getClassLoader()call from the initial class is the class loader that is associated with PrintClassLoaders. The same class loader instance is used as the system class loader returned by ClassLoader.getSystemClassLoader(), and its immediate parent is the extension class loader ExtClassLoader. The extension class loader has nullas its parent, which implies the bootstrap class loader. As a result of class loaders delegating to the parent before trying to load a class themselves, the classes found on the boot class path are always loaded by the bootstrap class loader. If the class is not found on the boot class path, the extensions class path is checked next. Only if a class is not found on either path is the application class path checked. If the class is not found by the application class loader as well, the notorious ClassNotFoundexception is thrown.
Several important points about class loading require a clear understanding:
n The class loader that has loaded and defined a class process is associated with it and referred to as the defining class loader.
n The loaded class is identified not just by its name, but by a pair of the name and the defining class loader. Two classes that share the same name but have different class loaders are deemed to be different.
n If class A makes a reference to class B that is not loaded yet, A’s defining class loader is used to load B.
Thus, if a class was loaded from the boot class path by the bootstrap class loader, it is inca- pable of referring to the classes from the application class path, unless it explicitly goes through the system class loader or a custom class loader. A thread has an associated class loader that can be obtained using getContextClassLoader(). It is typically set to be the class loader of the class that started the thread. Sometimes you need to use the thread context class loader rather than the defining class loader to access classes from a different source. This can be the case for servlets loaded using a custom class loader of the Web container. The container’s class loader might be configured to load only the classes from the Web applica- tion, which means the classes on the system class path are inaccessible.