C. E. McDowell
Computer Science Dept., University of California
Santa Cruz, CA 95064
email: charlie@cs.ucsc.edu, phone: (408) 459-4772
The possiblity for class unloading is included in the Java Language Specification. We believe the current specification allows for some unnecessary non-determinism related to static fields. Specifically a static field can be reinitialized, contrary to what many Java programmers believe. We believe that classes with static fields should not be unloaded until the class loader that loaded the class is no longer reachable.
Keywords: Java, class unloading, garbage collection
The Java Language Specification (JLS)[GJS96] allows for the unloading of classes. Without class unloading, a Java Virtual Machine (JVM) is like an OS that never releases the memory allocated for the code space of an application, even after the application has completed. In particular the JLS states that ``This can be used, for example, to unload a group of related types... Such a group might consist of all the classes implementing a single applet.'' Class unloading is important for any long running Java program that continuously loads classes that are used for some time and then no longer used. This is exactly the type of behavior that occurs with applets.
An undesirable (or at least potentially unexpected) result of class unloading as implemented in JDK1.1.2, the free Java system from JavaSoft, is that a static field in a class can get reinitialized. This is in direct conflict with section 8.3.1.1 of the Java Language Specification which states that for static fields ``there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created.'' Unfortunately this is not simply an error in the JDK1.1.2 implementation of the JVM, in fact JavaSoft may not even consider it a bug. It is certainly an ambiguity in the JLS and it raises the question of how you can provide class unloading in such a way as to preserve the expected behavior for static fields in classes.
There are two general ways a class can get loaded. The most common way is for the class name, call it ClassB, to actually appear in some other class, call it ClassA. The class won't actually get loaded until the program performs some action that requires ClassB to be loaded, e.g. creates an instance or references a static member or field of ClassB. The actual loading of ClassB could happen anytime but if there is a problem with the loading of ClassB then that error cannot be reported until the action in ClassA occurs that would require ClassB.
The other way for a class to be loaded
is some form of loading where the class name only
appears as a String such as with the method forname in class
java.lang.Class (see JLS section 20.3.8[GJS96]),
or the use of method loadClass from a custom class loader
extending class ClassLoader.
According to the JLS section 12.8, the only restrictions on class unloading are:
Class objects.
One obvious way for a Class object to be reachable is if there is a normal Java
reference to the Class object (e.g. a variable of type Class that
references the class in question).
But a class may also be considered reachable when there are no explicit
Class type variables referencing the Class object.
For example, we believe
ClassB is directly reachable
from ClassA if the source for ClassA contains a reference to ClassB as
a class. This is not a variable of type Class but the actual use of
the class name, for example in a declaration. Specifically the
constant pool for ClassA will contain
an occurrence of ClassB as a class value.
The constant pool is the part of the class file format that stores
references to other classes. For unresolved classes
the constant pool will contain
a string that is the actual class name associated with a tag
indicating this is a class name. For resolved classes
the constant pool will contain a direct reference to the Class object.
If a class was loaded using the forname method from class Class
or loadClass from ClassLoader,
then in JDK1.1.2 the Class object is no longer
reachable when all references to the object returned by the call to
forname have been removed.
In contrast, if the class was loaded by
explicit reference to the class name as a type,
then the class will continue to be reachable as long as the class
containing the explicit reference is reachable.
The class will be reachable even when all instances
of the class have been made unreachable and no user visible variables of type
Class reference the class.
We propose the following definition of ``reachable'' for a class.
A class is reachable if there are instances of the class or if there are reachable classes that contain resolved or unresolved references to the class.Including unresolved references as reachable classes is different from the behavior we observed in JDK1.1.2. Unfortunately, even using our definition of reachable, it is possible to have a class become unreachable, and then later become reachable again. The class that becomes unreachable and then later reachable does not need to have been loaded using
forname, although it would appear that
the class must have been loaded indirectly as the result of a call to
forname (see the example in the following section).
A solution we will describe below is to add an additional requirement
for class unloading - do not unload a class if the class loader that
loaded the class is still reachable and the class contains static
fields.
An unfortunate result of this
restriction is that no classes loaded by the default class loader will
ever be unloaded.
In JDK1.1.2, class unloading
is done at the same time
as garbage collection, although this may not be the best policy.
The following example demonstrates how both a class that is loaded by
a call to forname (ClassOne in the example)
and a class that is loaded by an explicit use of the class
as a type (ClassTwo in the example) are unloaded.
In addition there is an explicit use of ClassOne as a type in
main
yet the class still gets unloaded by JDK1.1.2. ClassOne would not be
unloaded using our definition of reachable because the constant pool for
the class Example contains a reference to ClassOne.
Our strict interpretation of the JLS concerning static fields
would prevent any unloading of a
class with static variables if the class was loaded by the default
class loader. We believe that the loader that loaded a class must also
be unreachable before a class can be unloaded and the default class
loader is never unreachable during the lifetime of a JVM invocation.
The following code example demonstrates
that Sun does not observe our strict interpretation of this in JDK1.1.2.
In this example, the creation of the object first assigned to
class_one_object (actually an instance of ClassOne) also creates an
instance of ClassTwo which contains a static field.
Once the references to the ClassOne object and the Class
object are
set to null, a call to the garbage collector results in both ClassOne
and ClassTwo being unloaded.
This can be seen because when the
final assignment to class_one_object occurs, creating a new instance of
ClassOne and a new instance of ClassTwo,
the program will print
out the value of counter in ClassTwo as 1 indicating it was
reinitialized to 0, clearly contradicting the language specification with
regard to static fields.
We believe the program should not unload ClassTwo in this example.
If the class is not unloaded then the program will
print out 1 and then 2, which it does if the
call to the garbage collector, and hence class unloading, is removed.
Another disturbing
aspect of this program is that it is clearly non-deterministic with
respect to when garbage collection occurs.
One might be tempted to simply dismiss this as a bug in JDK1.1.2, which it may well be, however, this example clearly illustrates a problem with class unloading. What is the right thing to do? Can you ever unload a class with a static field? If not, this might very well stop the unloading of most classes that we would like to unload.
class Example
{
public static void main(String[] args)
throws java.io.IOException, ClassNotFoundException,
IllegalAccessException, InstantiationException
{
int count = 0;
SomeInterface class_one_object;
/* load the class ClassOne and instantiate an instance of it */
/* ClassOne uses ClassTwo which will thus get loaded also */
Class theClass = Class.forName("ClassOne");
class_one_object = (SomeInterface)theClass.newInstance();
/* remove all references to the class and the instance */
class_one_object = null;
theClass = null;
System.gc(); /* force garbage collection which also unloads classes */
/*loads and instantiates ClassOne again. ClassTwo also gets reloaded*/
class_one_object = (SomeInterface)new ClassOne();
}
}
public class ClassOne implements SomeInterface{
public ClassOne(){
new ClassTwo();
}
}
public interface SomeInterface {
}
public class ClassTwo {
static int counter=0;
public ClassTwo(){
counter++;
System.out.println("ClassTwo has counter = " + counter);
}
}
We propose that unloading of system classes with static fields (i.e. any classes loaded by the default class loader) be disallowed. In addition a separate class loader should be used for each ``application'' that may load classes that will need to be unloaded.
Java provides a mechanism for a Java program to load classes under the control of a custom class loader. Two classes with the same name and identical class files will be considered different to a JVM if loaded using different class loaders, even if the class loaders are simply two instances of the same class loader class. Once the class loader object is no longer reachable, then the class unloading policy stated in the JLS can safely be applied (i.e. no instances of the class exist and the class object itself is not reachable). It would be impossible for the same class to be reloaded because ``sameness'' includes having been loaded by the same class loader which is no longer accessible.
Although current browsers, to our knowledge, do not yet implement class unloading, they do allocate one class loader for each distinct applet code base. In this way, if the browser determines, using any criteria it wishes, to terminate the execution of all applets loaded from a particular code base, then those classes loaded via that class loader can safely be unloaded. Specifically, it would be impossible for an applet to detect the reloading and consequent reinitialization of a static field without resorting to external, persistent resources.
Based on our proposal, no system classes containing static fields will be unloaded. This is not how JDK1.1.4 operates. It is clearly possible to have ``system'' classes (i.e. those found in the CLASSPATH) unloaded, as demonstrated in the example earlier. For systems with large amounts of memory or with support for virtual memory, having most or all system classes loaded during steady state may not be a problem. This restriction might be undesirable for memory limited embedded systems.
If it was necessary to unload system classes, then the ``OS'' could split the true system classes from the other classes that would currently be loaded by the default class loader. By true system class we mean one that must be loaded by the default class loader for security reasons. These later classes could then be loaded by the class loader used to load the actual application (e.g. the applet).
This use of class loaders would allow a programmer to write a Java program that functioned as the user interface to the ``OS'' (i.e. the one running on top of the JVM) and allowed for unloading of all classes loaded by an application when the ``OS'' detected that the application had terminated.
In this paper we have identified a problem with the Java Language Specification with regard to class unloading and static fields. We have provided an example that demonstrates how the current implementation of class unloading can result in a static field being reinitialized resulting in a program that changes its behavior depending upon when garbage collection occurs. We then describe a possible solution, specifically, a class with static fields should not be unloaded until the class loader that loaded the class is no longer reachable.