“Nobody notices when things go well.” | ||
--Zimmerman's Law of Complaints |
IN THIS CHAPTER
Class loaders load 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 bytecode.
The Java virtual machine running on top of the operating system (OS) provides a level of abstraction to the applications written in Java. Language specification and standardized 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 application, 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.
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 instrumentation 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 initialized 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 bootstrap class loader, which is implemented in native code and is used to load Java core classes such as java.lang.Object
and java.lang.ClassLoader
. The extensions class loader is used to load the extension libraries typically from the lib/ext
directory of JRE, and the JAR files placed in that directory are automatically available to Java applications. The application class loader is created internally by the JVM launcher to load classes from the standard CLASSPATH
. Finally, a class loader called the system class 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 information about their sources of class data.
Table 14.1. Class Loaders and Their CLASS
File Sources
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.
Example 14.1. PrintClassLoaders
Source 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 PrintClassLoaders
class is located in the covertjava.classloader
package. Running the class produces the following output:
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 AppClassLoader
instance 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 null
as 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 ClassNotFound
exception is thrown.
Several important points about class loading require a clear understanding:
The class loader that has loaded and defined a class process is associated with it and referred to as the defining class loader.
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.
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 incapable 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 application, which means the classes on the system class path are inaccessible.
Java gives applications control over how the classes are created or loaded through custom or user-defined class loaders. For example, Web and application servers provide each deployed application with its own class loader to better isolate the logical applications running inside the same JVM. Recall that, even if two classes were loaded from the same class file, they are deemed to be different if they were defined by different class loaders. With each application having its own class loader, static variables used so often to implement singleton patterns and store global data become applicationwide rather than JVM-wide. Custom class loaders also allow Web and application servers to reload classes without restarting the JVM. Another powerful technique is using a custom class loader to either create classes on-the-fly or alter the binary structure of the class data before defining it in the Java runtime.
In this section we are going to create a custom class loader called DecoratingClassLoader
in package covertjava.classloader
that allows modification of loaded binary class data on-the-fly. After it's coded, this class can be used to support encrypted class files or bytecode decoration, which are discussed later in this book. For now, we will focus on loading the classes from a user-defined class path and invoking a callback method that will have an opportunity to examine and modify the binary data. The code will be loosely based on JUnit's implementation of a class loader. All class loaders must extend the abstract class java.lang.ClassLoader
and, at minimum, override the findClass
method to load or create the class for a given name. DecoratingClassLoader
's implementation of findClass
is shown in Listing 14.2.
Example 14.2. findClass
Implementation
protected Class findClass(String name) throws ClassNotFoundException { System.out.println("DecoratingClassLoader loading class " + name); byte[] bytes = getClassBytes(name); if (getDecorator() != null) bytes = getDecorator().decorateClass(name, bytes); return defineClass(name, bytes, 0, bytes.length); }
The findClass
method is called only if the class is not found by any parent class loader in the hierarchy. This way, the loading of core classes such as java.lang.Object
is done by the bootstrap class loader. After the binary class data is loaded, the decorator is given a chance to augment the bytecode. Then the class bytes are passed to the defineClass
method of java.lang.ClassLoader
, which converts the binary class representation to an internal Class
object. The reading of binary class data is performed in method getClassBytes()
, shown in Listing 14.3.
Example 14.3. getClassBytes
Implementation
protected byte[] getClassBytes(String className) throws ClassNotFoundException { byte[] bytes = null; for (int i = 0; i < classPathItems.size(); i++) { String path = (String)classPathItems.get(i); String fileName = className.replace('.', '/') + ".class"; if (isJar(path) == true) { bytes = loadJarBytes(path, fileName); } else { bytes = loadFileBytes(path, fileName); } if (bytes != null) break; } if (bytes == null) throw new ClassNotFoundException(className); return bytes; }
The method iterates the items of the configured class path and attempts to locate and load the data from a .class
file in the corresponding directory. If the class data is not found, ClassNotFoundException
is thrown. DecoratingClassLoader
takes the class path as a parameter to its constructor, and the default constructor uses the value of the decorate.class.path
system property to initialize its class path. The full source code for the DecoratingClassLoader
can be found in the CovertJava/src/covertjava/classloader
directory.
To use the custom class loader, it must be explicitly specified for the loading of a class that is inaccessible to the parent class loaders. This can be done by passing a class loader instance to the Class.forName()
method or by directly calling the loadClass()
method of the custom class loader. Remember that class loaders delegate the loading to the parent chain and, if a parent class loader can locate the class, the child class loader's findClass()
method is not called. This means that the cleanest way of allowing a custom class loader to load a class is to ensure that the class is not found on the system class path, boot class path, or extension class path. This is a common choice for Web server and application server implementations that caution against placing application classes on the system CLASSPATH
. If full control of the class loading is required, the loadClass(
String name
, boolean resolve)
method can be overridden. This method is called to initiate the class loading, and the default implementation lets the parent attempt to load the class first. By overriding this method, the custom class loader is called for every class in the system, regardless of where the class data is found. An alternative is to use the bootstrap class loader as a parent by passing a null
value to the ClassLoader
constructor. However, taking the jars and directories with classes that are supposed to be loaded using a custom class loader off the system CLASSPATH
is the best alternative because it achieves the separation of system and custom classes.
To test the delegation of DecoratingClassLoader
, we will add a decorator called PrintingClassDecorator
that simply prints the name of the class on which it is invoked (see Listing 14.4).
To see our DecoratingClassLoader
in action, let's try it on the PrintClassLoaders
class we looked at earlier. We'll need a launcher class that instantiates our custom class loader and then loads the test class using it. DecoratingLauncher
in package covertjava.classloader
takes the name of the test class to launch and uses DecoratingClassLoader
to load it. It then invokes the main()
function of the test class via the reflection API. The main()
method of DecoratingLauncher
is shown in Listing 14.5.
Example 14.5. DecoratingLauncher main()
Method
public static void main(String[] args) throws Exception { if (args.length < 1) { System.out.println("Missing command line parameter <main class>"); System.out.println("Syntax: DecoratingLauncher " + "[-Ddecorate.class.path=<path>] <main class> [<arg1>, [<arg2>]..]"); System.exit(1); } DecoratingClassLoader decoratingClassLoader = new DecoratingClassLoader(); decoratingClassLoader.setDecorator(new PrintingClassDecorator()); Class mainClass = Class.forName(args[0], true, decoratingClassLoader); String[] mainArgs = new String[args.length - 1]; System.arraycopy(args, 1, mainArgs, 0, args.length - 1); invokeMain(mainClass, mainArgs); }
Notice that we are passing the instance of our class loader to the forName()
method. Before running the test, we need to ensure that the PrintClassLoaders
class is removed from CLASSPATH
and placed in a directory that will be pointed to by the decorate.class.path
system property. We'll have to modify the release target in our build.xml
to move covertjava.classloaders.PrintClassLoaders
to the CovertJava/lib/classes
directory before we create a JAR. We also need to create a new batch file in the bin
directory that will invoke the launcher and pass it PrintClassLoaders
as a parameter. The batch file content is shown in Listing 14.6.
Example 14.6. classLoaderTest.bat
Code
@echo off rem Demonstration of using custom class loader on PrintClassLoaders class call setEnv.bat set JAVA_OPTS=-Ddecorate.class.path=..libclasses %JAVA_OPTS% java %JAVA_OPTS% covertjava.classloader.DecoratingLauncher covertjava.classloader.PrintClassLoaders
Running classLoaderTest.bat
produces the following output:
Processed bytes for class covertjava.classloader.PrintClassLoaders System class loader = sun.misc.Launcher$AppClassLoader@12f6684 Thread context class loader = sun.misc.Launcher$AppClassLoader@12f6684 Class loader hierarchy for this class: covertjava.classloader.DecoratingClassLoader@ad3ba4 sun.misc.Launcher$AppClassLoader@12f6684 sun.misc.Launcher$ExtClassLoader@f38798 null (bootstrap class loader)
From the output, we can see that the class loader associated with the PrintClassLoaders
class is an instance of DecoratingClassLoader
. DecoratingClassLoader
got the system class loader as its parent and the rest of the hierarchy is the same.
Class loaders load and initialize classes and interfaces in the JVM.
The application loading process begins with the initial class passed to the launcher. All parent classes and any classes referenced by the main()
method of the initial class are lazily loaded and initialized as the references are made.
The bootstrap class loader is implemented in native code and used to load Java core classes, such as java.lang.Object
and java.lang.ClassLoader
.
The class loaders are organized in a chain, in which a child lets the parent find a class before attempting to find the class itself.
A loaded class is identified not just by its name, but by a pair of the name and the defining class loader.
Custom class loaders enable Java applications to take control of class loading. They are used to implement the reloading of classes, the separation of logical applications inside the same JVM, and the decoration or creation of the bytecode on-the-fly.
3.144.96.40