- 87 - by you. If the object is not supposed to be changed, you can throw an exception on any update
method. Alternatively, if you want some objects to be canonicalized but with copy-on-write behavior, you can allow the updater to return a noncanonicalized copy of the canonical object.
[5]
Beware that using a subclass may break the superclass semantics.
Note that it makes no sense to build a table of millions or even thousands of strings or other objects if the time taken to test for, access, and update objects in the table is longer than the time
you are saving canonicalizing them.
4.2.4.3 Weak references
One technique for maintaining collections of objects that can grow too large is the use of
WeakReference
s from the
java.lang.ref
package in Java 2. If you need to maintain one or more pools of objects with a large number of objects being held, you may start coming up against
memory limits of the VM. In this case, you should consider using
WeakReference
objects to hold on to your pool elements. Objects referred to by
WeakReference
s can be automatically garbage- collected if memory gets low enough see
Reference Objects .
Reference Objects
In many ways, you can think of
Reference
objects as normal objects that have a private
Object
instance variable. You can access the private object termed the referent using the
Reference.get
method. However,
Reference
objects differ from normal objects in one hugely important way. The garbage collector may be allowed to clear
Reference
objects when it decides space is low enough . Clearing the
Reference
object sets the referent to
null
. For example, say you assign an object to a
Reference
. Later you test to see if the referent is
null
. It could be
null
if, between the assignment and the test, the garbage collector kicked in and decided to reclaim space:
Reference ref = new WeakReferencesomeObject; ref.get is someObject at the moment
Now do something that creates lots of objects, making the garbage collector try to find more memory space
doSomething ; now test if ref is null
if ref.get == null System.out.printlnThe garbage collector deleted my ref;
else System.out.printlnref object is still here;
Note that the referent can be garbage-collected at any time, as long as there are no other strong references referring to it. In the example,
ref.get
can become
null
only if there are no other non-
Reference
objects referring to
someObject
. The advantage of
Reference
s is that you can use them to hang on to objects that you want to reuse but are not needed immediately. If memory space gets too low, those
objects not currently being used are automatically reclaimed by the garbage collector. This means that you subsequently need to create objects instead of reusing them, but that
is preferable to having the program crash from lack of memory. To delete the reference object itself when the referent is nulled, you need to create the reference with a
ReferenceQueue
instance. When the reference object is cleared, it is added to the
- 88 -
ReferenceQueue
instance and can then be processed by the application, e.g., explicitly deleted from a hash table in which it may be a key.
There are three
Reference
types in Java 2.
WeakReference
s and
SoftReference
s differ essentially in the order in whcih the garbage collector clears them. Basically, the garbage
collector does not clear
WeakReference
objects until all
SoftReference
s have been cleared.
PhantomReference
s not addressed here are not cleared automatically by the garbage collector and are intended for use in a different way.
The concept behind this differentiation is that
SoftReference
s are intended to be used for caches that may need to have memory automatically freed, and
WeakReference
s are intended for canonical tables that may need to have memory automatically freed.
The rationale is that caches normally take up more space and are the first to be reclaimed when memory gets low. Canonical tables are normally smaller, and developers prefer
them not to be garbage-collected unless memory gets really low. This differentiation between the two reference types allows cache memory to be freed up first if memory gets
low; only when there is no more cache memory to be freed does the garbage collector start looking at canonical table memory.
Java 2 comes with a
java.util.WeakHashMap
class that implements a hash table with keys held by weak references.
A
WeakReference
normally maintains references to elements in a table of canonicalized objects. If memory gets low, any of the objects referred to by the table and not referred to anywhere else in the
application except by other weak references are garbage-collected . This does not affect the canonicalization because only those objects not referenced anywhere else are removed. The
canonical object can be re-created when required, and this new instance is now the new canonical object: remember that no other references to the object exist, or the original could not have been
garbage-collected.
For example, a table of canonical
Integer
objects can be maintained using
WeakReference
s. This example is not particularly useful: unlike the earlier example, in which
Integer
objects from 1 to 10 can be referenced directly with no overhead, thus providing a definite speedup for tests, the next
example has overheads that would probably swamp any benefits of having canonical
Integer
s. I present it only as a clear and simple example to illustrate the use of
WeakReference
s. The example has two iterations: one sets an array of canonical
Integer
objects up to a value set by the command-line argument; a second loops through to access the first 10 canonical
Integer
s. If the first loop is large enough or the VM memory is constrained low enough, the garbage collector
kicks in and starts reclaiming some of the
Integer
objects that are all being held by
WeakReference
s. The second loop then reaccesses the first 10
Integer
objects. Earlier, I explicitly held on to five of these
Integer
objects integers 3 to 7 inclusive in variables so that they could not be garbage-collected, and so that the second loop would reset only the five reclaimed
Integer
s. When running this test with the VM constrained to 4 MB:
java -Xmx4M tuning.reuse.Test 100000
you get the following output:
Resetting integer 0 Resetting integer 1
- 89 -
Resetting integer 2 Resetting integer 8
Resetting integer 9
The example is defined here. Note the overheads. Even if the reference has not been garbage- collected, you have to access the underlying object and cast it to the desired type:
package tuning.reuse; import java.util.;
import java.lang.ref.; public class Test
{ public static void mainString[] args
{ try
{ Integer ic = null;
int REPEAT = args.length 0 ? Integer.parseIntargs[0] : 10000000; Hang on to the Integer objects from 3 to 7
so that they cannot be garbage collected Integer i3 = getCanonicalInteger3;
Integer i4 = getCanonicalInteger4; Integer i5 = getCanonicalInteger5;
Integer i6 = getCanonicalInteger6; Integer i7 = getCanonicalInteger7;
Loop through getting canonical integers until there is not enough space, and the garbage collector reclaims some.
for int i = 0; i REPEAT; i++ ic = getCanonicalIntegeri;
Now just re-access the first 10 integers 0 to 9 and the 0, 1, 2, 8, and 9 integers will need to be reset in
the access method since they will have been reclaimed for int i = 0; i 10; i++
ic = getCanonicalIntegeri; System.out.printlnic;
} catchException e{e.printStackTrace ;}
} private static Vector canonicalIntegers = new Vector ;
public static Integer getCanonicalIntegerint i {
First make sure our collection is big enough if i = canonicalIntegers.size
canonicalIntegers.setSizei+1; Now access the canonical value.
This element contains null if the the value has never been set or a weak reference that may have been garbage collected
WeakReference ref = WeakReference canonicalIntegers.elementAti; Integer canonical_i;
if ref == null {
never been set, so create and set it now canonical_i = new Integeri;
canonicalIntegers.setElementAtnew WeakReferencecanonical_i, i; }
- 90 -
else if canonical_i = Integer ref.get == null {
has been set, but was garbage collected, so recreate and set it now Include a print to see that we are resetting the Integer
System.out.printlnResetting integer + i; canonical_i = new Integeri;
canonicalIntegers.setElementAtnew WeakReferencecanonical_i, i; }
else clause not needed, since the alternative is that the weak ref was present and not garbage collected, so we now have our canonical integer
return canonical_i; }
}
4.2.4.4 Enumerating constants