On 05/10/2013 12:52 PM, Peter Levart wrote:
While executing the above test with the patch to ReferenceHandler
applied, I noticed a strange behaviour. I can reproduce this behaviour
reliably on both JDK7 and JDK8. When the patch is applied as proposed:
try {
lock.wait();
} catch (InterruptedException |
OutOfMemoryError x) { }
... I still get the following output from the test (reliably, always):
Exception: java.lang.OutOfMemoryError thrown from the
UncaughtExceptionHandler in thread "Reference Handler"
Exception in thread "main" java.lang.Exception: Reference Handler
thread died.
at OOMEInReferenceHandler.main(OOMEInReferenceHandler.java:80)
But when i change the patch to the following:
try {
lock.wait();
} catch (OutOfMemoryError |
InterruptedException x) { }
...the test reliably and always passes.
My explanation to his behaviour is that the order of exception
handlers changes the order of class referencing. In the former
variation (that still throws OOME) the following seems to be
happening:
wait() is interrupted and InterruptedException instance creation is
attempted. Because this is the 1st reference to InterruptedException
class in the lifetime of the JVM, loading of InterruptedException
class is attempted which fails because of OOME. This OOME is caught by
handler and ignored. But after handling of this OOME, another
reference to InterruptedException class is attempted by exception
handlers themselves (I don't know how exception handlers work exactly,
but I have a feeling this is happening). Because InterruptedException
class was not successfully loaded the 1st time tried, every reference
to this class must throw NoClassDefFoundError, so this is attempted,
but creation of NoClassDefFoundError fails because there's no heap
space and another OOME is thrown - this time out of exception handling
block, which is propagated and kills the thread.
If the order of exception handlers is reversed, this second OOME is
caught and ignored.
Hi,
This really seems to be happening (at least approximately, see below)
because if InterruptedException class is preloaded at start of test,
the
order of exception handling does not have any impact on test.
By disassembling the class-files of both variants, I found the only
difference is the order of OutOfMemoryError & InterruptedException
entries found in exception table:
catch (InterruptedException | OutOfMemoryError x) variant has:
public void run();
Code:
0: invokestatic #2 // Method
java/lang/ref/Reference.access$100:()Ljava/lang/ref/Reference$Lock;
3: dup
4: astore_2
5: monitorenter
6: invokestatic #3 // Method
java/lang/ref/Reference.access$200:()Ljava/lang/ref/Reference;
9: ifnull 33
12: invokestatic #3 // Method
java/lang/ref/Reference.access$200:()Ljava/lang/ref/Reference;
15: astore_1
16: aload_1
17: invokestatic #4 // Method
java/lang/ref/Reference.access$300:(Ljava/lang/ref/Reference;)Ljava/lang/ref/Reference;
20: invokestatic #5 // Method
java/lang/ref/Reference.access$202:(Ljava/lang/ref/Reference;)Ljava/lang/ref/Reference;
23: pop
24: aload_1
25: aconst_null
26: invokestatic #6 // Method
java/lang/ref/Reference.access$302:(Ljava/lang/ref/Reference;Ljava/lang/ref/Reference;)Ljava/lang/ref/Reference;
29: pop
30: goto 48
* 33: invokestatic #2 // Method
java/lang/ref/Reference.access$100:()Ljava/lang/ref/Reference$Lock;**
** 36: invokevirtual #7 // Method
java/lang/Object.wait:()V**
** 39: goto 43*
42: astore_3
43: aload_2
44: monitorexit
45: goto 0
48: aload_2
49: monitorexit
50: goto 60
53: astore 4
55: aload_2
56: monitorexit
57: aload 4
59: athrow
60: aload_1
61: instanceof #10 // class sun/misc/Cleaner
64: ifeq 77
67: aload_1
68: checkcast #10 // class sun/misc/Cleaner
71: invokevirtual #11 // Method
sun/misc/Cleaner.clean:()V
74: goto 0
77: aload_1
78: getfield #12 // Field
java/lang/ref/Reference.queue:Ljava/lang/ref/ReferenceQueue;
81: astore_2
82: aload_2
83: getstatic #13 // Field
java/lang/ref/ReferenceQueue.NULL:Ljava/lang/ref/ReferenceQueue;
86: if_acmpeq 95
89: aload_2
90: aload_1
91: invokevirtual #14 // Method
java/lang/ref/ReferenceQueue.enqueue:(Ljava/lang/ref/Reference;)Z
94: pop
95: goto 0
Exception table:
from to target type
* 33 39 42 Class java/lang/InterruptedException**
** 33 39 42 Class java/lang/OutOfMemoryError*
6 45 53 any
48 50 53 any
53 57 53 any
catch (OutOfMemoryError | InterruptedException x) variant has the
exactly same bytecodes but the following exception table:
Exception table:
from to target type
* 33 39 42 Class java/lang/OutOfMemoryError**
** 33 39 42 Class java/lang/InterruptedException*
6 45 53 any
48 50 53 any
53 57 53 any
... so what seems to be happening is a little different but similar to
what I have explained. In the former variant (that still throws OOME),
the handler 1st checks for the type of thrown exception against
InterruptedException class, which fails and attempts to throw
NoClassDefFoundError which can't be allocated so another OOME is
thrown,
but in the later variant the 1st check is against OutOfMemoryError
class
which succeeds, so the empty handler is executed and no more checks are
made (no 2nd reference to InterruptedException class).
The fix I proposed in previous mail works (OOME is thrown twice and 2nd
OOME is handled), but also the following would work (if the order of
checks follows the source in every compiler):
try {
lock.wait();
} catch (OutOfMemoryError x) { }
catch (InterruptedException x) { }
...the benefit of this is that OOME is never thrown two times.
Regards, Peter