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
|