| 
 | 
| 
 
Sponsored Link • 
 
 | 
| 
 Advertisement 
 | 
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 SurpriseThe 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.

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