Deadlock while removing PhantomReference from ReferenceQueue

Why does the following cause a deadlock? Tested under 1.5 and 1.6. Why garbage collector doesn't (eventually) enqueue the reference? Anyhow, the reference queue doesn't seem to have a clue, and waits indefinitely...

import java.lang.ref.PhantomReference;

import java.lang.ref.Reference;

import java.lang.ref.ReferenceQueue;

publicclass NewMain{

publicstaticvoid main(String[] args)throws Exception{

Object o =new Object();

ReferenceQueue q =new ReferenceQueue();

PhantomReference ref =new PhantomReference(o, q);

o =null;

//System.gc(); // <-- if this is commented ...

ref = (PhantomReference)q.remove();// ... then this creates a deadlock!

System.out.println("done");

}

}

Instead of System.gc(), I can achieve a similar effect using something like:

for (int i = 0; i < 100000000; i++)new Object().toString();

Any ideas?

Message was edited by:

ounos

[1710 byte] By [ounosa] at [2007-11-26 13:32:34]
# 1
The method remove() blocks - as documented.It that what you mean by 'deadlock'?
jschella at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 2

Indeed it blocks as documented, but it does so indefinitely, even if the object is obviously reclaimable, and the queue should pick up the reference sometime in the future.

Maybe this is an artifact of the gc implementation, for instance it could think it is ok not to collect some memory if the program doesn't actively ask for more. If it is so, then this example proves this implementation to be dangerous.

Can anybody verify that this behavior doesn't only occur to me?

-edit- Please note how System.gc() actually changes the outcome of the program, not merely runtime performance. I think this is not acceptable at all, it should be a side-effect (regarding what the user program can see) free method.

ounosa at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 3
Use remove(timeout) if you don't want it block indefinitely http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ref/ReferenceQueue.html#remove(long)
AjaySingh516a at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 4
AjaySingh516, the problem is that no matter how much time I wait, the queue doesn't get the reference that it should...
ounosa at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 5

> Maybe this is an artifact of the gc implementation, for instance it

could think it is ok not to collect some memory if the program doesn't

actively ask for more. If it is so, then this example proves this

implementation to be dangerous.

May be the object is not put on the collection queue (check if

WeakReference helps). I know one library that is doing it very well,

commons-httpclient. Look at

http://jakarta.apache.org/commons/httpclient/xref/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.html

AjaySingh516a at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 6

The behavior I describe is identical for weak and phantom references. (For soft references I can't easily reproduce it, as System.gc() will ignore the soft-reachable object, as there is plenty of memory).

import java.lang.ref.*;

public class NewMain {

public static void main(String[] args) throws Exception {

Object o = new Object();

ReferenceQueue q = new ReferenceQueue();

Reference[] refs = { new WeakReference(o, q), new PhantomReference(o, q) };

o = null;

//System.gc(); // if this is not commented, the queue gets both references

Reference ref = null;

do {

ref = q.remove(1000); // If no System.gc(), always returns null!

System.out.println("got ref: " + ref);

} while (ref == null);

}

}

ounosa at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 7

Your original NewMain compiles to the following bytecode: 0:new#2; //class java/lang/Object

3:dup

4:invokespecial#1; //Method java/lang/Object."<init>":()V

7:astore_1

8:new#3; //class java/lang/ref/ReferenceQueue

11: dup

12: invokespecial#4; //Method java/lang/ref/ReferenceQueue."<init>":()V

15: astore_2

16: new#5; //class java/lang/ref/PhantomReference

19: dup

20: aload_1

21: aload_2

22: invokespecial#6; //Method java/lang/ref/PhantomReference."<init>":(Lj

ava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V

25: astore_3

26: aconst_null

27: astore_1

28: aload_2

29: invokevirtual#7; //Method java/lang/ref/ReferenceQueue.remove:()Ljava

/lang/ref/Reference;

32: checkcast#5; //class java/lang/ref/PhantomReference

35: astore_3

36: getstatic#8; //Field java/lang/System.out:Ljava/io/PrintStream;

39: ldc#9; //String done

41: invokevirtual#10; //Method java/io/PrintStream.println:(Ljava/lang/St

ring;)V

44: return

However, I don't believe that the VM spec would prohibit JITs from optimising away instructions 26 and 27. As a general rule, if you're playing with reference types you should probably assume that a reference to an object could still be on the stack until the method in which it was created returns or throws an exception up the stack.

YAT_Archivista at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 8

Thanks for the feedback, YAT_Archivist . That sounds reasonable. Though, it seems like the garbage collector declines to run when not needed, even if no other thread is running. That's the conclusion I drew from my last attempt trying to see the object getting reclaimed (made the object reference public, even volatile, and let the stack into which the object was created, die; these should pretty much deny optimization tricks)

import java.lang.ref.*;

public class NewMain {

public static void main(String[] args) throws Exception {

new NewMain();

}

public volatile Object o;

public NewMain() throws Exception {

o = new Object();

ReferenceQueue q = new ReferenceQueue();

Reference[] refs = { new WeakReference(o, q), new PhantomReference(o, q) };

o = null;

new Work(q).start();

}

}

class Work extends Thread {

private ReferenceQueue q;

Work(ReferenceQueue q) {

this.q = q;

}

public void run() {

try {

Reference ref = null;

do {

ref = q.remove(1000); //still, always returns null

System.out.println("got ref: " + ref);

} while (ref == null);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

}

What a lazy gc that is!

ounosa at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 9

> What a lazy gc that is!

You do want your VM to run in a multi-process OS and not to deny your other apps a shot at the processor, don't you?

You could try concurrent GC - see http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html for a full list of the options available with 1.5. There may be a similar doc for 1.6 somewhere, but I don't know whether anything changed.

YAT_Archivista at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...
# 10

When I am testing the gc I like to turn on -Xincgc.

Also you may want to consider creating a situation where the GC actually needs to find objects like limiting the heap size and making your objects huge. Otherwise there is no point in the GC wasting time to reclaim one lonely object that hardly uses any space at all. And GC will typically be designed not to do so.

Furthermore, its a bit difficult for the GC to detect reclaimable objects that were used by the current thread local memory. So for the GC to reclaim this object it requires more than a casual examanation. Thus the GC does not catch these on a first pass typically.

The GC is most aggressive in clearing phantom references, then weak references, then finally soft references. Of course, no phantom can be cleared as long as there is a soft or weak against the same object, and no weak can be cleared as long as there is a soft against the same object.

_dnoyeBa at 2007-7-7 22:10:45 > top of Java-index,Core,Core APIs...