Chapter 14. Controlling Class Loading

 

“Nobody notices when things go well.”

 
 --Zimmerman's Law of Complaints

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.

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 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.

The JVM from a class loading perspective.

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 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

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.jar and a few other standardJARs. It can be manipulated using the -Xbootclasspath command-line parameterto the Java launcher.

Extensions

Directories listed in the system property to the lib/ext directory of the JRE. However, it can be explicitly specified at the java.ext.dirs, which by default points 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 CLASSPATH environment 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.

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.

Writing a Custom Class Loader

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).

Example 14.4. PrintingClassDecorator Implementation

public class PrintingClassDecorator implements ClassDecorator {

    public byte[] decorateClass(String name, byte[] bytes) {
        System.out.println("Processed bytes for class " + name);
        return bytes;
    }
}

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.

Quick Quiz

1:

What is the purpose of a class loader?

2:

What is the bootstrap class loader, and which classes does it load?

3:

What is the extensions class loader, and which classes does it load?

4:

Which classes are considered to be the same inside JVM?

5:

What can a custom class loader be used for?

6:

Can a custom class loader be used to load all classes (including core Java classes)?

7:

Why does DecoratingClassLoader use its own class path?

In Brief

  • 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.96.40