Name: boT120536 Date: 07/30/2001
java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1-b24)
Java HotSpot(TM) Client VM (build 1.3.1-b24, mixed mode)
Normally all soft references cleared by the garbage collector are added
to the reference queue the reference was created with (if any). But if
a program happens to call SoftReference.get() after the reference has
been cleared, but before it has been added to the reference queue it
will never be added to the reference queue.
The program below illustrates the problem. The test class is a simplified
version of a cache implementation using soft references to allow the garbage
collector to remove cache entries.
The test code keeps track of the number of expected entries in the cache
by counting the number of entries added and removed.
When running the program, the first output column is the difference
between the actual number of entries in the cache and the expected number,
i.e. when the first column is non-zero something is wrong.
The last column shows the number of times SoftReference.get() has been
called and returned null. In my tests, the size mismatch (the first column)
always is the same as the number of null returns from get() (but with the
opposite sign).
Note that the program might need to run for a while for the problem to
show up, the last output column must be non-zero for the necessary
conditions to be present. Since the garbage collector is involved,
adjusting the maximum memory allowed may speed things up. The problem
seems to happen most frequently when the garbage collector clears
large numbers of soft references.
If the program is run with one or more arguments, the bug workaround
is enabled.
----------[SoftRefTest.java]----------
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Random;
public class SoftRefTest
{
HashMap map;
ReferenceQueue refQueue;
Random random;
int numRemoved;
int numNullGets;
boolean doWorkAround;
public SoftRefTest(boolean doWorkAround)
{
map = new HashMap();
refQueue = new ReferenceQueue();
random = new java.util.Random();
numRemoved = 0;
numNullGets = 0;
this.doWorkAround = doWorkAround;
}
public Object get(Object key)
{
removeBroken();
MySoftRef ref = (MySoftRef) map.get(key);
if (ref != null) {
Object result = ref.get();
if (result == null) {
if (doWorkAround) {
// this object has been cleared by the garbage
// collector but will _not_ be added to the
// reference queue. do workaround.
map.remove(key);
numRemoved++;
}
numNullGets++;
}
return result;
}
else {
return null;
}
}
public void put(Object key, Object value)
{
removeBroken();
map.put(key, new MySoftRef(key, value, refQueue));
}
protected void removeBroken()
{
MySoftRef broken;
while ((broken = (MySoftRef) refQueue.poll()) != null) {
Object key = broken.key;
if (map.get(key) != null) {
map.remove(key);
numRemoved++;
}
}
}
class MySoftRef extends SoftReference
{
public Object key;
public MySoftRef(Object key, Object value, ReferenceQueue queue)
{
super(value, queue);
this.key = key;
}
}
public void test()
{
int numAdded = 0;
for (int n = 0; n < 1000; n++) {
for (int i = 0; i < 1000; i++) {
Integer r = new Integer(random.nextInt(100000));
if (get(r) == null) {
// add ~1 Kbyte objects
put(r, new Object[256]);
numAdded++;
}
}
int size = map.size();
int expectedSize = numAdded - numRemoved;
if (n % 50 == 0)
System.out.println("\nsize-expectedSize size numAdded " +
"numRemoved numNullGets\n");
System.out.println("" + (size - expectedSize) + " " + size + " " +
numAdded + " " + numRemoved + " " + numNullGets);
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
}
}
}
public static void main(String[] args)
{
SoftRefTest test = new SoftRefTest(args.length > 0);
test.test();
}
}
----------[end of SoftRefTest.java]----------
(Review ID: 128187)
======================================================================