Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Technical Tips

Tech Tips archive

Tech Tips

October 31, 2000

WELCOME to the Java Developer ConnectionSM (JDC) Tech Tips, October 31, 2000. This issue is about class loaders. When a Java program doesn't load successfully, developers usually suspect the class path. In reality, the class path is a special case of a powerful class loading architecture built around the java.lang.ClassLoader class. This tip covers:

This issue of the JDC Tech Tips is written by Stuart Halloway, a Java specialist at DevelopMentor (http://www.develop.com/java).

These tips were developed using JavaTM 2 SDK, Standard Edition, v 1.3.


CLASS LOADERS AS A NAMESPACE MECHANISM

Program developers must cope with the problem of name collisions. This is true for all programming environments. If you name a class BankAccount there is a good chance that somebody else will use the same name for another class. Sooner or later the two classes will collide in the same process, wreaking havoc. Programmers who begin to use the JavaTM programming language learn to use packages to prevent name collisions. Instead of naming a class BankAccount you place the class in a package, perhaps naming the class something like com.develop.bank.BankAccount (that is, the reverse of your domain name). Hopefully there is minimal danger of a name collision with this approach.

However with a language as dynamic as the Java programming language, "minimal danger" is not safe enough. The package naming scheme relies on the cooperation of all developers in a system. This is difficult to coordinate and is error-prone. Imagine, for example, the confusion that would result if the European and American branches of a company each created a com.someco.FootballPlayer class! More importantly, Java applications can run for a long time--long enough that you might recompile and ship a new version of a class without ever shutting down the application. This leads to multiple versions of the same class trying to live in the same application.

The JavaTM Virtual machine1 handles these problems through its class loader architecture. Every class in an application is loaded by an associated ClassLoader object. How does this solve the name collision problem? The VM treats classes loaded by different class loaders as entirely different types, even if their packages and names are exactly the same. Here's a simple example:

import java.net.*;

public class Loader {
  public static void main(String [] args) 
    throws Exception
  {
    URL[] urlsToLoadFrom = 
      new URL[]{new URL(
        "file:subdir/")};
    URLClassLoader loader1 = 
      new URLClassLoader(urlsToLoadFrom);
    URLClassLoader loader2 = 
      new URLClassLoader(urlsToLoadFrom);
    Class cls1 = 
      Class.forName("Loadee",
        true, loader1);
    Class cls2 = 
      Class.forName("Loadee",
        true, loader2);
    System.out.println("(cls1 == cls2)
      is " 
      + ((cls1 == cls2) ? "true" :
        "false"));
  }
}
                                                       
//place Loadee in a subdir named 'subdir'
public class Loadee {
  static {
    System.out.println("Loadee class
      loaded");
  }
}

Compile the Loader class, then compile the Loadee class in a subdirectory named "subdir." When you run the Loader class, you should see the output:

    Loadee class loaded
    Loadee class loaded
    (cls1 == cls2) is false        

Both cls1 and cls2 are named Loadee. In fact, they both come from the same .class file (although that does not need to be the case, in general). Nevertheless, the VM treats cls1 and cls2 as two separate classes.


RELATING CLASS LOADERS TO THE CLASS PATH

The example above is interesting, but most developers do not code that way. Instead of using the reflection method Class.forName() to load classes into the VM, they simply write code such as:

// load a Foo (don't bother me about 
// where it came from...)
  Foo f = new Foo();

In order for this to compile and run, the Foo class must be in the class path (or in a few other special locations beyond the scope of this tip). The class path is the place where most developers interact with class loaders, although implicitly. But from the perspective of the VM, the class path is just a special case of the class loader architecture.

When a Java application starts, it creates a class loader that searches a set of URLs. The application initializes the class loader to use file URLs based on the values in the class path. Assuming that the application's main class is in the class path, the main class is loaded and begins executing. After that, class loading is implicit. Whenever a class refers to another class, for example, by initializing a field or local variable, the referent is immediately loaded by the same class loader that loaded the referencing class. Here's an example:

// compile these classes all in one 
// file ReferencingClass.java
class Referent {
  static {
    System.out.println("Referent
      loaded by " + 
      Referent.class.getClassLoader());
  }
}

public class ReferencingClass {
  public static void main(String [] args) {
    System.out.println("ReferencingClass
      loaded by " + 
      ReferencingClass.class.getClassLoader());
    //refer to Referent
    new Referent();
    System.out.println("String loaded by
      " + 
      String.class.getClassLoader());
  }
}

When you compile and run the ReferencingClass, you should see that both ReferencingClass and Referent are loaded by the same class loader. (If you are using the JDKTM it will be a nested class of sun.misc.Launcher.) However, the String class is loaded by a different class loader. In fact the String class is loaded by the "null" class loader, even though String is also referenced by ReferencingClass. This is an example of class loader delegation. A class loader has a parent class loader, and the set of a class loader and its ancestors is called a delegation.

Whenever a class loader loads a class, it must consult its parent first. A standard Java application begins with a delegation of three class loaders: the system class loader, the extension class loader, and the bootstrap class loader. The system class loader loads classes from the class path, and delegates to the extension class loader, which loads Java extensions. The parent of the extension class loader is the the bootstrap class loader, also known as the null class loader. The bootstrap class loader loads the core API.

The delegation model has three important benefits. First, it protects the core API. Application-defined class loaders are not able to load new versions of the core API classes because they must eventually delegate to the boostrap loader. This prevents the accidental or malicious loading of system classes that might corrupt or compromise the security of the VM. Second, the delegation model makes it easy to place common classes in a shared location. For example, in a servlet engine the servlet API classes could be placed in the class path where they can be shared. But the actual servlet implementations might be loaded by a separate URLClassLoader so that they can be reloaded later. Third, the delegation model makes it possible for objects loaded by different class loaders to refer to each other through superclasses or superinterfaces that are loaded by a shared class loader higher in the delegation.


USING CLASS LOADERS FOR HOT DEPLOYMENT

The ability to load mutiple classes with the same name into the virtual machine allows servers to partition processing into separate namespaces. This partitioning could be space-based, separating code from different sources to simplify security. For example, applets from two different codebases could run in the same browser process. Or, the partitioning could be time-based. Here, new versions of a class could be loaded as they become available. This time-based partitioning feature is sometimes known as hot deployment. The following code demonstrates hot deployment in action.

//file ServerItf.java
public interface ServerItf {
  public String getQuote();
}

//file Client.java
import java.net.URL;
import java.net.URLClassLoader;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Client {
    static ClassLoader cl;
    static ServerItf server;
    
    public static void loadNewVersionOfServer() 
      throws Exception {
        URL[] serverURLs = 
          new URL[]{new URL("
            file:server/")};
        cl = new URLClassLoader(serverURLs);
        server = (ServerItf) cl.loadClass(
          "ServerImpl").newInstance();
    }
    
    public static void test() throws Exception {
        BufferedReader br = new BufferedReader(
          new InputStreamReader(System.in));
        loadNewVersionOfServer();
        while (true) {
            System.out.print(
              "Enter QUOTE, RELOAD, GC, or
                QUIT: ");    
            String cmdRead = br.readLine();
            String cmd = cmdRead.toUpperCase();
            if (cmd.equals("QUIT")) {
                return;
            } else if (cmd.equals("
              QUOTE")) {
                System.out.println(
                  server.getQuote());
            } else if (cmd.equals("
              RELOAD")) {
                loadNewVersionOfServer();
            } else if (cmd.equals("
              GC")) {
                System.gc();
                System.runFinalization();
            }
        }
    }
    
    public static void main(String [] args) {
        try {
            test();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// file ServerImpl.java.  Place this file 
// in a subdirectory named 'server'.
class Reporter {
    Class cls;
    
    Reporter(Class cls) {
        this.cls = cls;
        System.out.println(
          "ServerImpl class " +
            cls.hashCode() + 
            " loaded into VM");
    }
    protected void finalize() {
        System.out.println("ServerImpl
          class " + 
          cls.hashCode() + " unloaded
            from VM");
    }
}
public class ServerImpl implements ServerItf {

    //catch the class being unloaded from the VM
    static Object reporter = 
      new Reporter(ServerImpl.class);
    
    public String getQuote() {
        return "A rolling stone gathers no
          moss";
    }
}

Compile the Client and ServerItf files in a directory, and then compile the ServerImpl in a subdirectory named "server." Make sure to include the higher level directory on your class path when you compile ServerImpl, for example:

javac -classpath \mypath\ServerImpl.java

When you start the Client, you should see the following prompt:

Enter QUOTE, RELOAD, GC, or QUIT:

Enter QUOTE to see the current quote from the server:

A rolling stone gathers no moss

Now, without shutting down the process, use another console or GUI to edit the ServerImpl class. Change the getQuote method to return a different quote, for example, "Wet birds do not fly at night." Recompile the server class. Then return to the console where the Client is still running and enter RELOAD. This invokes the method loadNewVersionOfServer(), which uses a new instance of URLClassLoader to load a new version of the server class. You should see something like this:

ServerImpl class 7434986 loaded into VM

Reissue the QUOTE command. You should now see your new version of the quote, for example:

Wet birds do not fly at night

Notice that you did this without shutting down your application. This same technique is used by servlet engines such as Apache Software Foundation's Tomcat to automatically reload servlets that have changed.

There are a few interesting points about explicitly using class loaders in your application. First, instances of Class and ClassLoader are simply Java objects, subject to the normal memory rules of the Java(tm) platform. In other words, when classes and class loaders are no longer referenced, they can be reclaimed by the garbage collector. This is important in a long-running application, where unused old versions of classes could waste a lot of memory. However, making sure that classes are unreferenced can be tricky. Every instance has a reference to its associated class, every class has a reference to its class loader, and every class loader has a reference to every class it ever loaded. It's easy to view this tangled knot of references as a class loader "hairball." If you have a reference to any object in the hairball, none of the objects can be reclaimed by the garbage collector. In the simple Client application above, you can verify by inspection that all references to the old class loader and its classes are explicitly dropped when a new class loader is created. If you need more proof, you can issue the GC command from the Client console. On a VM with a reasonably aggressive GC implementation, you should see a log message indicating that the old ServerImpl class has been reclaimed by the garbage collector.

Notice that the Client code never refers to ServerImpl directly. Instead, the ServerImpl instance is held in a reference of type ServerItf. This is critical to making explicit use of class loaders. Remember the rules about implicit class loading, and imagine what would happen if the Client had a field of type ServerImpl. When the VM needs to initialize that field, it uses the Client's class loader to try to load ServerImpl. Client is the application main class, so it is loaded from the class path by the system class loader. Because the ServerImpl class is not on the class path, the reference to it causes a NoClassDefFoundError. Don't be tempted to "fix" this by placing ServerImpl in the class path. If you do that, the ServerImpl class will indeed load, but it will load under control of the system class loader. This defeats hot deployment because your instances of URLClassLoader delegate to the system class loader. So, no matter how many times you create a URLClassLoader, you always get the copy of ServerImpl that was originally loaded from the class path.

For more information on class loaders, see:


— Note —

Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun MicrosystemsTM purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click the Update button.

— Subscribe —

To subscribe to a JDC newsletter mailing list, go to the Subscriptions page (http://developer.java.sun.com/subscription/), choose the newsletters you want to subscribe to, and click Update.

— Feedback —

Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com

— Archives —

You'll find the JDC Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html

— Copyright —

Copyright 2000 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This Document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html

JDC Tech Tips October 31, 2000

_______
1 As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.


[ This page was updated: 29-Jan-2001 ]
Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary | Feedback | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-2001 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy.