HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

I haven't found a good discussion on the topic of class loading and unloading in my searches through Java literature or Java resources on the Web, so I thought it would be a good topic to cover this month. This month's column is all about how and when Java classes are loaded and unloaded, and how you can write classes that link into that process.

Let's start off with class loading. Java classes are loaded using objects of type java.lang.ClassLoader. Each VM has exactly one default class loader called the system class loader. Basically, when the VM detects that a particular class needs to be loaded, say a class named "Foo", the VM asks its system class loader to find "Foo". It's the class loader's job to find some resource that defines that class. It is not of the VM's concern whether that resource exists in a .CLASS file on the local file system, in a .CLASS file accessed through an HTTP server over the Internet, in a .ZIP file (or in some other location) or is even generated on-the-fly by the class loader itself. That's the class loader's concern. It's the class loaders job to find that resource somehow and load it using the method ClassLoader.defineClass().

Most system class loaders work pretty much the same way. The system class loader is aware of a system property called the classpath. The classpath is a list of local file system directories and/or .ZIP files. (And, starting with Java 1.1, it might also include .JAR files, which are a lot like .ZIP files.) Conceptually, this classpath is a list of locations to find .CLASS files.

When asked to load class "Foo", a system class loader will create the file name "Foo.class" by just sticking the extension ".class" onto the class name. The system class loader will search for this file using the entries in the classpath. For example, if the classpath contains the list of entries "A;B;C", three directory names, the system class loader will first try to find the file with the full path "A\Foo.class". Failing that, it will then look for "B\Foo.class". And finally, failing that, it will look for the file "C\Foo.class". If that third step fails, the system class loader will throw an exception of type ClassNotFoundException.

Assuming the system class loader finds the file "Foo.class" in one of the directories on the classpath, it will load the resource into the VM using ClassLoader.defineClass(). This is where things start to get interesting. The defineClass() method does several steps first to ensure the .CLASS resource is in the right format and the code in the class will not harm the VM (a process known as "verification"). Once that is done, defineClass() will create a new object of type java.lang.Class to represent the newly loaded Java class. This new object is known as the "Class object". The Class object represents the loaded class to Java code. For example, to access the Class object of any object, you can call the object's getClass() method:

// Accessing the Class object of Object "o"
Class cls = o.getClass();
String classname = cls.getName();

Getting back to class loading, Java classes can have dependencies on each other. This means that in order to load class A, it is also required to load class B. In this case, we would say class A is dependent on class B. Class dependencies are created whenever you explicitly refer to class B in class A's code. For example:

// Class Foo is dependent on class Bar
public class Foo {
public void xyz() {
Bar barObject = new Bar(); // explicit dependency
}
...
}

The explicit dependency of class Foo on class Bar means that the class Bar will automatically be loaded when it is required to be used by class Foo. These dependencies between classes are automatically detected and made to load required classes when they are needed. That is, when the method xyz() is called in class Foo, the Bar class will automatically be loaded and a corresponding Class object created.

There are several ways an explicit dependency can be set up:

  • Defining a class variable or local variable of type Bar in Foo creates a dependency of Foo on Bar.
  • Declaring a return value of type Bar in a method of class Foo creates a dependency of Foo on Bar.
  • Deriving Foo directly from class Bar creates a dependency of Foo on Bar.
Note that there is one other step that is completed before the Class object is returned from defineClass(). That is the "static initialization" step and it happens as the last step of class loading. Each class has a static initializer block. This is a block of code that is to be run as soon as the class is loaded - before any objects of the class are created or any methods of the class called. The purpose of the static initializer block is to initialize the static members of the class and to do any other special initialization required for the class to work. Other special initializations include loading dynamic libraries that implement native code, making network or database connections that might be required, etc. Take a look at the three classes in Listing 1, which demonstrate these static initializer blocks. Both classes Foo and Bar have static initializer blocks. Class Foo is dependent on class Bar. The Main class implements a main() method (a stand-alone application), and Main is dependent on Foo because Foo is mentioned as a variable type in class Main. The static initializer blocks just print out a line of text to System.out. This is the output you would see if you ran Main using the JDK 1.1 interpreter:
Loading class Main
Starting the program
Loading class Foo
Created the Foo object
Loading class Bar

Starting with Java 1.1, classes could be unloaded just like they could be loaded. The rule for unloading a class is surprisingly simple and makes sense. When the Class object for a class is garbage collected, the class is automatically unloaded. Use of the garbage collector and the Class objects to represent the loaded state of a class is very elegant, understandable and intuitive.

Note: When an explicit dependency is set up between two classes, the required class will not be unloaded until the dependent class is unloaded. There is an explicit relationship set up between the Class objects. That is, as long as the Class object for class Foo sticks around (and the Foo class is thus loaded into memory), the Class object for Bar will stick around.

It is possible to set up "implicit" dependencies between classes. An implicit dependency means that the VM is not aware of the dependency. Instead, your program takes control of loading classes when they are required. To force the system class loader to load a class, you use the static method Class.forName (String classname). Listing 2 shows a simple class Foo which loads the class Bar using code, instead of using an explicit reference to the Bar class. This prevents an explicit relationship between Foo and Bar. In this case, it is possible for the class Bar to be loaded and unloaded, and even reloaded, several times during the life of the VM. The program in Listing 2 attempts to force the VM to garbage collect the Bar class Class object by filling memory with orphan objects every time the ENTER key is pressed by the user. Sample output from this program running in the JDK 1.1 is:

***Loading Foo class
Starting the application
***Loading Bar class
Filling memory with 1000 useless objects
<enter key pressed by user>
Filling memory with 1000 useless objects
<enter key pressed by user>
***Loading Bar class
Filling memory with 1000 useless objects
...

Note that the Bar class static initializer was called twice. That is, the Bar class was unloaded at some point and then reloaded when it was later required.

The fact that, starting with Java 1.1, classes could be unloaded, leads to an important feature actually missing from Java. While it is easy for me to write code that is called when a class is loaded (the static initializer block), there is no companion block called when a class is unloaded. The static initializer allows me to initialize static members, but there is no static uninitializer block that allows me to clean up resources.

When would such a static uninitializer be useful? Imagine I've created a static-only class that allows me to allocate and deallocate chunks of native memory. The public interface for this NativeMemory class might look something like this:

public class NativeMemory {
public static int alloc(int size); // returns handle
public static void setmem(int handle, byte[]);
public static void dealloc(int handle);
public static int realloc(int handle, int newsize);
...
}

For such a NativeMemory class, a static uninitializer would be crucial. Without it, there would be no way for the class to release allocated native memory that the client code forgot to deallocate.

It would be great if you could provide a finalize() method to be used by the NativeMemory Class object. The class is unloaded when the garbage collector finalizes the Class object. This would be the perfect time for the NativeMemory class to release any native memory that hasn't been released yet. The finalize() method would, in effect, become the static uninitializer for the class.

Unfortunately, it is not possible to change the finalize() method implementation for the Class class. Instead, however, you can delegate the responsibility of class uninitialization to another object. A reference to that object would be stored in a static variable in the NativeMemory class. This means that the clean-up object would only be garbage-collected when the NativeMemory Class object was being collected. That is, when the NativeMemory class was being unloaded. The finalize() method of the delegate object would then become the static uninitializer for the NativeMemory class.

Listing 3 shows how a NativeMemory class could use a static reference to a delegate object to manage its static class uninitialization. The delegate object would, ideally, be a static inner class object of the NativeMemory class which would allow the delegate access to the private static member of the NativeMemory class.

Note that, like object finalization, class unloading is not guaranteed to occur at any particular time. Better VM implementations will unload classes that haven't been used recently and that are no longer being referenced. Worse implementations may never unload unused classes. The best you can do is to write your classes with static uninitializers when required, just in case the VM does a good job unloading your class.

So, the point of this column has been threefold:

  • To give a primer on class loading for those Java programmers who might not have a good idea of how it works
  • To explain how explicit and implicit class relationships are important
  • And something for those who already know all about class loading: How to make static uninitializers for your classes
One final note: If your class really requires static uninitialization, even if the application quits because System.exit() is called, there is a new facility in Java 1.1 that ensures object clean up on exit. Take a look at the new System.runFinalizersOnExit() method.

About the Author
Brian Maso is President of Blumenfeld & Maso, Inc., a Java and Media consulting company. In addition to his involvement in several commercial Java projects, he is also the author of several Java books, including Osborne/McGraw-Hill's upcoming title "Visual J++ From the Ground Up". Brian is the Java guru of DevelopMentor's Java training courses. He has also written several Java white-papers for individual corporations. Brian can be reached via e-mail at [email protected]

	

Listing 1: Class dependency. Main is dependent on Foo, is dependent on Bar.
  
public class Main {  
  public static void main(String[] astrArgs) {  
    System.out.println("Starting the program");  
    Foo f = new Foo();  
    System.out.println("Created the Foo object");  
    f.xyz();  
  }  

  // static initializer  
  static {  
    System.out.println("Loading class Main");  
  }  
}  

class Foo {  
  static {  
    System.out.println("Loading class Foo");  
  }  

  public void xyz() {  
    Bar bar = new Bar();  
  }  
}  

class Bar {  
  static {  
    System.out.println("Loading class Bar");  
  }  
}  

Listing 2.
  
The implicit dependancy of class Foo on class Bar is caused 
by the fact that the Foo code uses Class.forName() to load class Bar. 
This means the Bar class may be unloaded and reloaded several 
times during the lifetime of the Foo class.  

public class Main {  
  static Foo f = new Foo();  

  public static void main(String[] astrArgs) {  
    System.out.println("Starting the application");  

    Thread t = new Thread(f);  
    t.start();  

    try {  
      while(true) {  
        synchronized(f) {  
          System.in.read();  
          f.notify();  
        }  
      }  
    } catch (Exception e) {  
      // poor exception handling,  
      // for the sake of brevity  
    }  
  }  
}  

class Foo implements Runnable {  
  static {  
    System.out.println(  
        "Loading Foo class");  
  }  

  public Foo() {}  

  public synchronized void run() {  
    while(true) {  
      System.out.println(  
          "Filling memory with " +  
          "1000 useless objects");  

      Class cls = Class.forName("Bar");  
      for(int ii=0 ; ii<1000 ; ii++)  
        cls.newInstance();  

      cls = null;  
      System.gc();  

      try {  
        wait();  
      } catch (InterruptedException ie) {  
      }  
    }  
  }  
}  

class Bar {  
  static {  
    System.out.println(  
        "***Loading Bar class");  
  }  

  public Bar() {}  
}  

Listing 3:
 
A static-only NativeMemory class requires a static uninitializer. 
The static inner class object fulfills that function.  
public class NativeMemory {  
  private static uniniter = new Uninit();  
  ...  

  // Static initializer loads native library,  
  // starts allocating chucks of memory, etc.  
  static {  
    ...  
  }  

  // Public, static only interface  
  public static int alloc(int size) {   // returns handle  }  
  public static void setmem(int handle, byte[]) {...}  
  public static void dealloc(int handle) {...}  
  public static int realloc(int handle, int newsize) {...}  
  ...  

  // Private static inner class, a singleton class,  
  // the singleton object acts as a class uninitializer  
  // for this class  
  private static class Uninit {  
    public Uninit() {}  

    // Called only when NativeMemory class  
    // ready to be unloaded.  
    public void finalize() {  
      // Has access to private static members of the  
      // NativeMemory class. Uses them to  
      // uninitialize the class.  
    }  
  }  
}
  
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.