The Artima Developer Community
Sponsored Link

Chapter 8 of Inside the Java Virtual Machine
The Linking Model
by Bill Venners

<<  Page 19 of 20  >>

Advertisement

Example: Unloading Unreachable Greeters

As an example of dynamically loaded types becoming unreachable and getting unloaded by the virtual machine, consider the following application:

// On CD-ROM in file linking/ex7/GreetAndForget.java
import com.artima.greeter.*;

public class GreetAndForget {

    // Arguments to this application:
    //     args[0] - path name of directory in which class files
    //               for greeters are stored
    //     args[1], args[2], ... - class names of greeters to load
    //               and invoke the greet() method on.
    //
    // All greeters must implement the com.artima.greeter.Greeter
    // interface.
    //
    static public void main(String[] args) {

        if (args.length <= 1) {
            System.out.println(
                "Enter base path and greeter class names as args.");
            return;
        }

        for (int i = 1; i < args.length; ++i) {
            try {

                GreeterClassLoader gcl =
                    new GreeterClassLoader(args[0]);

                // Load the greeter specified on the command line
                Class c = gcl.loadClass(args[i]);

                // Instantiate it into a greeter object
                Object o = c.newInstance();

                // Cast the Object ref to the Greeter interface type
                // so greet() can be invoked on it
                Greeter greeter = (Greeter) o;

                // Greet the world in this greeter's special way
                greeter.greet();

                // Forget the class loader object, Class
                // instance, and greeter object
                gcl = null;
                c = null;
                o = null;
                greeter = null;

                // At this point, the types loaded through the
                // GreeterClassLoader object created at the top of
                // this for loop are unreferenced and can be unloaded
                // by the virtual machine.
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

The GreetAndForget application accepts the same command line arguments as the Greet application of the previous example. The first argument is a base directory path name where the GreetAndForget application will look for greeters. Subsequent arguments are greeter names. To understand this example you should be familiar with the Greet application presented earlier in this chapter.

Imagine you invoke the GreetAndForget application with the following command line:

java GreetAndForget greeters Surprise HiTime Surprise
The code for the HiTime greeter, which selects a different greeting based on the time of day, is shown above in the previous section of this chapter. The code for the Surprise greeter, which pseudo-randomly selects one of four helper greeters-- Hello, Greetings, Salutations, or HowDoYouDo--and invokes its greet() method, is shown here:
// On CD-ROM in file linking/ex7/greeters/Surprise.java
import com.artima.greeter.Greeter;

public class Surprise implements Greeter {

    public void greet() {

        // Choose one of four greeters pseudo-randomly and
        // invoke its greet() method.
        int choice = (int) (Math.random() * 3.99);

        Greeter g;

        switch(choice) {

        case 0:
            g = new Hello();
            g.greet();
            break;

        case 1:
            g = new Greetings();
            g.greet();
            break;

        case 2:
            g = new Salutations();
            g.greet();
            break;

        case 3:
            g = new HowDoYouDo();
            g.greet();
            break;
        }
    }
}

Given the command line shown above, the GreetAndForget application invokes the greet() method of the Surprise greeter first, then the HiTime greeter, then the Surprise greeter again. GreetAndForget's actual output would vary depending on the time of day and Surprise's pseudo-random mood. For the purposes of this example, assume that you typed in the above command, hit return, and got the following output:

How do you do, globe!
Good afternoon, world!
Greetings, planet!
This output indicates Surprise chose to execute HowDoYouDo's greet() method the first time around and Greetings's greet() method the second time around.

The first pass through GreetAndForget's for loop, the virtual machine loads the Surprise class and invokes its greet() method. The constant pool for Surprise includes a symbolic reference to each of the four helper greeters that it may choose: Hello, Greetings, Salutations, and HowDoYouDo. Assuming the Java virtual machine that you used to run the GreetAndForget application uses late resolution, only one of these four symbolic references will be resolved during the first pass of GreetAndForget's for loop: the symbolic reference to HowDoYouDo. The virtual machine resolves this symbolic reference when it executes the bytecodes that correspond to the following statement in Surprise's greet() method:

g = new HowDoYouDo();

To resolve the symbolic reference from Surprise's constant pool to HowDoYouDo, the virtual machine invokes the GreeterClassLoader object's loadClass() method, passing the string "HowDoYouDo" in the name parameter. The virtual machine uses the GreeterClassLoader object to load HowDoYouDo because Surprise was loaded through the GreeterClassLoader object. As mentioned earlier in this chapter, when the Java virtual machine resolves a symbolic reference, it uses the same class loader that defined the referencing type (in this case, Surprise) to initiate loading the referenced type (in this case, HowDoYouDo).

Once Surprise's greet() method has created a new HowDoYouDo instance, it invokes its greet() method:

g.greet();

As the virtual machine executes HowDoYouDo's greet() method, it must resolve two symbolic references from HowDoYouDo's constant pool-- one to class java.lang.System and another to class java.io.PrintStream. To resolve these symbolic references, the virtual machine invokes the GreeterClassLoader object's loadClass() method, once with the name java.lang.System and once with the name java.io.PrintStream. As before, the virtual machine uses the GreeterClassLoader object to load these classes because the referencing class--in this case, HowDoYouDo--was loaded through the GreeterClassLoader object. But these two classes, both members of the Java API, will end up being loaded by the bootstrap class loader anyway, because loadClass() will first delegate to its parent.

Remember that before the loadClass() method of GreeterClassLoader attempts to look for a requested type in the base directory (in this case, directory greeters), it invokes its parent, the system class loader. The system class loader will first delegate to its parent, which will first delegate to its parent, and so on. Eventually findSystemClass() will be invoked to delegate to the bootstrap class loader, the end-point of the parent-delegation chain. Because the bootstrap class loader (via findSystemClass()) is able to load both java.lang.System and java.io.PrintStream, the loadClass() method will simply return the Class instance returned by findSystemClass(). These classes will be marked not as having been loaded by the GreeterClassLoader object, but as having been loaded by the bootstrap class loader. To resolve any references from java.lang.System or java.io.PrintStream, the virtual machine will not invoke the loadClass() method of the GreeterClassLoader object, or even the system class loader. It will just use the bootstrap class loader directly.

As a result, after Surprise's greet() method has returned, there will be two types marked as having been loaded by the GreeterClassLoader object: class Surprise and class HowDoYouDo. These two types will be in the virtual machine's internal list of the types loaded by the GreeterClassLoader object.

Just after Surprise's greet() method returns, the Class instances for Surprise and HowDoYouDo are reachable by the application. The garbage collector will not reclaim the space occupied by these Class instances, because there are ways for the application's code to access and use them. See Figure 8-11 for a graphical depiction of the reachability of these two Class instances.

Figure 8-11. The reachability of the Class instances for Surprise and HowDoYouDo.

Figure 8-11. The reachability of the Class instances for Surprise and HowDoYouDo.

The Class instance for Surprise can be reached in two ways. First, it can be reached directly from local variable c of GreetAndForget's main() method. Second, it can be reached from local variables o and greeter, which both point to the same Surprise object. From the Surprise object, the virtual machine can get at Surprise's type data, which includes a reference to Surprise's Class object. The third way the Class instance for Surprise can be reached is through the gcl local variable of GreetAndForget's main() method. This local variable points to the GreeterClassLoader object, which includes a reference to a HashTable object in which a reference to Surprise's Class instance is stored.

The Class instance for HowDoYouDo can be reached in two ways. One way is identical to the one of the paths to the Class instance for Surprise: the gcl local variable of GreetAndForget's main() method points to the GreeterClassLoader object, which includes a reference to a HashTable object. The Hashtable contains a reference to HowDoYouDo's Class instance. The other way to reach HowDoYouDo's class instance is through Surprise's constant pool.

When the virtual machine resolved the symbolic reference from Surprise's constant pool to HowDoYouDo, it replaced the symbolic reference with a direct reference. The direct reference points to HowDoYouDo's type data, which includes a reference to HowDoYouDo's Class instance.

Thus, starting from Surprise's constant pool, the Class instance to HowDoYouDo is reachable. But why would the garbage collector look at direct references emanating from Surprise's constant pool in the first place? Because Surprise's Class instance is reachable. When the garbage collector finds that it can reach Surprise's Class instance, it makes sure it marks the Class instances for any types that are directly referenced from Surprise's constant pool as reachable. If Surprise is still live, the virtual machine can't unload any types Surprise may need to use.

Note that of the three ways, described above, that Surprise's Class instance can be reached, none of them involve a constant pool of another type. Surprise does not appear as a symbolic reference in the constant pool for GreetAndForget. Class GreetAndForget did not know about Surprise at compile-time. Instead, the GreetAndForget application decided at run-time to load and link to class Surprise. Thus, the Class instance for class Surprise is only reachable by starting from the local variables of GreetAndForget's main() method. Unfortunately for Surprise (and ultimately for HowDoYouDo), this does not constitute a very firm grasp on life.

The next four statements in GreetAndForget's main() method, will change the reachability situation completely:

// Forget the user-defined class loader, Class
// instance, and greeter object
gcl = null;
c = null;
o = null;
greeter = null;
These statements null out all four starting places from which Surprise's Class instance is reachable. As a result, after these statements have been executed, the Class instance for Surprise is no longer reachable. These statements also render unreachable the Class instance for HowDoYouDo, the Surprise instance that was formerly pointed to by the o and greeter variables, the GreeterClassLoader instance that was formerly pointed to by the gcl variable, and the Hashtable instance that was pointed to by the classes variable of the GreeterClassLoader object. All five of these objects are now available for garbage collection.

When (and if) the garbage collector gets around to freeing the unreferenced Class instances for Surprise and HowDoYouDo, it can also free up all the associated type data in the method area for Surprise and HowDoYouDo. Because these class's Class instances are unreachable, the types themselves are unreachable and can be unloaded by the virtual machine.

Note that two iterations of the for loop later (given the command line shown above), the GreetAndForget application will again load class Surprise. Keep in mind that the virtual machine will not reuse the type data for Surprise that was loaded during the first pass of the for loop. Granted, that type data became available for unloading at the end of the first pass. But even if the Class instance for Surprise hadn't become unreferenced at the end of the first pass, the type data from the first pass wouldn't be reused during the third pass.

With each pass of the for loop, the main() method of GreetAndForget creates a new GreeterClassLoader object. Thus, every greeter that GreetAndForget loads is loaded through a different user-defined class loader. For example, if you invoke the GreetAndForget application with the Hello greeter listed five times on the command line, the application will create five instances of class GreeterClassLoader. The Hello greeter will be loaded five times by five different user-defined class loaders. The method area will contain five different copies of the type data for Hello. The heap will contain five Class instances that represent the Hello class--one for each namespace into which Hello is loaded. When one of the Class instances for Hello becomes unreferenced, only the Hello type data associated with that particular Class instance would be available for unloading.

<<  Page 19 of 20  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use