Here is an experimental revision of the code that Dave posted; it is meant to demonstrate a number of things. The most important are:
- If you want to really observe a space leak, it is helpful to actually use objects which are large (
new int[X]
is your friend here). - If you want to really observe a space leak, it (unfortunately) seems like you may have to employ the (non-standard)
-Xms
and-Xmx
options to the JVM. - There are more reliable/sensible alternatives for monitoring whether an object has been collected than attempting to maintain a hashtable of object identifiers. Namely, use of classes in
java.lang.ref.*
.- Having said this, I admit that I do not fully understand the semantics of weak references, because I was not able to naively replace all of Dave's uses of
kill(int)
with mykillAlt(int)
procedure.
- Having said this, I admit that I do not fully understand the semantics of weak references, because I was not able to naively replace all of Dave's uses of
- Finalizers really are run only once. However, that doesn't always mean that you can't collect an object just because the call to its finalizer makes it live again.
I had to backport the code to JDK 1.4 since that's all I have on my poor little Mac OS X box. Sorry. I tried to make the backport clean by using trampoline instantiation of the generic collections rather than cluttering up the code with casts everywhere.
Sample command line invocations of this class:
% java -Xmx1mb -Xms1mb Immortal -dummysize 100000 -num 10
% java -Xmx1mb -Xms1mb Immortal -dummysize 100000 -blowup 10
(Note that due to the braindead way I'm doing argument parsing, the order of the options above is VERY significant).
import java.util.Hashtable;
import java.util.Vector;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
public class Immortal {
static class HashtableFromIntegerToInteger {
Hashtable me = new Hashtable();
public int get(int k) { return ((Integer)me.get(new Integer(k))).intValue(); }
public void put(int k, int v) { me.put(new Integer(k), new Integer(v)); }
public void remove(int k) { me.remove(new Integer(k)); }
}
static class HashtableFromIntegerToImmortal {
Hashtable me = new Hashtable();
public Immortal get(int k) { return ((Immortal)me.get(new Integer(k))); }
public void put(int k, Immortal v) { me.put(new Integer(k), v); }
public void remove(int k) { me.remove(new Integer(k)); }
}
static class HashtableFromIntegerToRef {
Hashtable me = new Hashtable();
public Reference get(int k) { return ((Reference)me.get(new Integer(k))); }
public void put(int k, Reference v) { me.put(new Integer(k), v); }
public void remove(int k) { me.remove(new Integer(k)); }
}
/** tracks the number of times each finalizer is run */
// private static HashtablefinalizeCounts = new Hashtable ();
private static HashtableFromIntegerToInteger finalizeCounts = new HashtableFromIntegerToInteger();
/** used to control object lifetimes */
private static HashtableFromIntegerToImmortal pointers = new HashtableFromIntegerToImmortal();
private static HashtableFromIntegerToRef phantoms = new HashtableFromIntegerToRef();
private static ReferenceQueue refqueue = new ReferenceQueue();
private static boolean nomesg = false;
private static void mesg(String s) {
if (! nomesg) {
System.out.println(s);
}
}
/** used to generate unique identifier codes */
private static int unique = 0;
/** some "large" state so that we can observe blowup */
public int[] dummydata;
private static int dummysize = 100;
public static void killAlt(int id) {
int finalizeCount = finalizeCounts.get(id);
pointers.remove(id);
while ( ! phantoms.get(id).isEnqueued())
{
mesg("(alt) trying to kill " + id + "...");
System.gc();
}
}
/** deletes the object with the given id. */
public static void kill(int id)
{
int finalizeCount = finalizeCounts.get(id);
pointers.remove(id);
while (finalizeCounts.get(id) == finalizeCount)
{
mesg("trying to kill " + id + "...");
System.gc();
}
}
// The code from these two methods can't be inlined, because
// we rely on their stack frame disappearing to prevent the
// link to the tracked object from persisting.
public static int makeTemporaryObject()
{
Immortal temp = new Immortal();
return temp.id;
}
public static void doSomethingWith(int id)
{
Immortal temp = pointers.get(id);
temp.sayHello();
}
/** identifier code */
private int id;
private Immortal()
{
id = unique++;
mesg("creating instance " + id);
finalizeCounts.put(id, 0);
pointers.put(id, this);
phantoms.put(id, new WeakReference(this, refqueue));
int last = dummysize;
dummydata = new int[last + 1];
dummydata[0] = 1;
dummydata[last] = 1;
}
public void sayHello()
{
System.out.println("hi, I'm number " + id);
}
public void finalize()
{
mesg("finalizing " + id + "...");
finalizeCounts.put(id, finalizeCounts.get(id) + 1);
// clear! *khzh-thump*
pointers.put(id, this);
}
public static void main(String[] args) {
if (args.length == 0) {
int id = Immortal.makeTemporaryObject();
// This causes the finalizer to run (in the GC thread.)
Immortal.kill(id);
// And yet, the object is still alive!
Immortal.doSomethingWith(id);
// This will now loop infinitely, since the finalizer
// will never be run a second time.
Immortal.kill(id);
} else {
for(int i = 0; i < args.length; i++) {
if (false) {
// dummy case for uniform syntax
} else if (args[i].equals("-nomesg")) {
nomesg = true;
} else if (args[i].equals("-dummysize")) {
int num = Integer.parseInt(args[++i]);
dummysize = num;
} else if (args[i].equals("-num")) {
int num = Integer.parseInt(args[++i]);
for(int j = 0; j < num; j++) {
int id = Immortal.makeTemporaryObject();
// This causes the finalizer to run (in the GC thread.)
Immortal.kill(id);
// And yet, the object is still alive!
Immortal.doSomethingWith(id);
// FSK: alternative variant of kill which uses weak references
Immortal.killAlt(id);
}
} else if (args[i].equals("-blowup")) {
int num = Integer.parseInt(args[++i]);
for(int j = 0; j < num; j++) {
int id = Immortal.makeTemporaryObject();
// This causes the finalizer to run (in the GC thread.)
Immortal.kill(id);
// And yet, the object is still alive!
Immortal.doSomethingWith(id);
}
}
}
}
}
}
No comments:
Post a Comment