<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
</head>
<body bgcolor="#FFFFFF" text="#000000">
<div class="moz-cite-prefix">On 05/10/2013 12:52 PM, Peter Levart
wrote:<br>
</div>
<blockquote cite="mid:518CD15F.70503@gmail.com" type="cite">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:
<br>
<br>
try {
<br>
lock.wait();
<br>
} catch (InterruptedException |
OutOfMemoryError x) { }
<br>
<br>
... I still get the following output from the test (reliably,
always):
<br>
<br>
Exception: java.lang.OutOfMemoryError thrown from the
UncaughtExceptionHandler in thread "Reference Handler"
<br>
Exception in thread "main" java.lang.Exception: Reference Handler
thread died.
<br>
at
OOMEInReferenceHandler.main(OOMEInReferenceHandler.java:80)
<br>
<br>
But when i change the patch to the following:
<br>
<br>
try {
<br>
lock.wait();
<br>
} catch (OutOfMemoryError |
InterruptedException x) { }
<br>
<br>
...the test reliably and always passes.
<br>
<br>
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:
<br>
<br>
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.
<br>
<br>
If the order of exception handlers is reversed, this second OOME
is caught and ignored.
</blockquote>
<br>
Hi,<br>
<br>
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.<br>
<br>
By disassembling the class-files of both variants, I found the only
difference is the order of OutOfMemoryError &
InterruptedException entries found in exception table:<br>
<br>
catch (InterruptedException | OutOfMemoryError x) variant has:<br>
<br>
<tt> public void run();</tt><tt><br>
</tt><tt> Code:</tt><tt><br>
</tt><tt> 0: invokestatic #2 // Method
java/lang/ref/Reference.access$100:()Ljava/lang/ref/Reference$Lock;</tt><tt><br>
</tt><tt> 3: dup </tt><tt><br>
</tt><tt> 4: astore_2 </tt><tt><br>
</tt><tt> 5: monitorenter </tt><tt><br>
</tt><tt> 6: invokestatic #3 // Method
java/lang/ref/Reference.access$200:()Ljava/lang/ref/Reference;</tt><tt><br>
</tt><tt> 9: ifnull 33</tt><tt><br>
</tt><tt> 12: invokestatic #3 // Method
java/lang/ref/Reference.access$200:()Ljava/lang/ref/Reference;</tt><tt><br>
</tt><tt> 15: astore_1 </tt><tt><br>
</tt><tt> 16: aload_1 </tt><tt><br>
</tt><tt> 17: invokestatic #4 // Method
java/lang/ref/Reference.access$300:(Ljava/lang/ref/Reference;)Ljava/lang/ref/Reference;</tt><tt><br>
</tt><tt> 20: invokestatic #5 // Method
java/lang/ref/Reference.access$202:(Ljava/lang/ref/Reference;)Ljava/lang/ref/Reference;</tt><tt><br>
</tt><tt> 23: pop </tt><tt><br>
</tt><tt> 24: aload_1 </tt><tt><br>
</tt><tt> 25: aconst_null </tt><tt><br>
</tt><tt> 26: invokestatic #6 // Method
java/lang/ref/Reference.access$302:(Ljava/lang/ref/Reference;Ljava/lang/ref/Reference;)Ljava/lang/ref/Reference;</tt><tt><br>
</tt><tt> 29: pop </tt><tt><br>
</tt><tt> 30: goto 48</tt><tt><br>
</tt><b><tt> 33: invokestatic #2 // Method
java/lang/ref/Reference.access$100:()Ljava/lang/ref/Reference$Lock;</tt></b><b><tt><br>
</tt></b><b><tt> 36: invokevirtual #7 //
Method java/lang/Object.wait:()V</tt></b><b><tt><br>
</tt></b><b><tt> 39: goto 43</tt></b><tt><br>
</tt><tt> 42: astore_3 </tt><tt><br>
</tt><tt> 43: aload_2 </tt><tt><br>
</tt><tt> 44: monitorexit </tt><tt><br>
</tt><tt> 45: goto 0</tt><tt><br>
</tt><tt> 48: aload_2 </tt><tt><br>
</tt><tt> 49: monitorexit </tt><tt><br>
</tt><tt> 50: goto 60</tt><tt><br>
</tt><tt> 53: astore 4</tt><tt><br>
</tt><tt> 55: aload_2 </tt><tt><br>
</tt><tt> 56: monitorexit </tt><tt><br>
</tt><tt> 57: aload 4</tt><tt><br>
</tt><tt> 59: athrow </tt><tt><br>
</tt><tt> 60: aload_1 </tt><tt><br>
</tt><tt> 61: instanceof #10 // class
sun/misc/Cleaner</tt><tt><br>
</tt><tt> 64: ifeq 77</tt><tt><br>
</tt><tt> 67: aload_1 </tt><tt><br>
</tt><tt> 68: checkcast #10 // class
sun/misc/Cleaner</tt><tt><br>
</tt><tt> 71: invokevirtual #11 // Method
sun/misc/Cleaner.clean:()V</tt><tt><br>
</tt><tt> 74: goto 0</tt><tt><br>
</tt><tt> 77: aload_1 </tt><tt><br>
</tt><tt> 78: getfield #12 // Field
java/lang/ref/Reference.queue:Ljava/lang/ref/ReferenceQueue;</tt><tt><br>
</tt><tt> 81: astore_2 </tt><tt><br>
</tt><tt> 82: aload_2 </tt><tt><br>
</tt><tt> 83: getstatic #13 // Field
java/lang/ref/ReferenceQueue.NULL:Ljava/lang/ref/ReferenceQueue;</tt><tt><br>
</tt><tt> 86: if_acmpeq 95</tt><tt><br>
</tt><tt> 89: aload_2 </tt><tt><br>
</tt><tt> 90: aload_1 </tt><tt><br>
</tt><tt> 91: invokevirtual #14 // Method
java/lang/ref/ReferenceQueue.enqueue:(Ljava/lang/ref/Reference;)Z</tt><tt><br>
</tt><tt> 94: pop </tt><tt><br>
</tt><tt> 95: goto 0</tt><tt><br>
</tt><tt> Exception table:</tt><tt><br>
</tt><tt> from to target type</tt><tt><br>
</tt><b><tt> 33 39 42 Class
java/lang/InterruptedException</tt></b><b><tt><br>
</tt></b><b><tt> 33 39 42 Class
java/lang/OutOfMemoryError</tt></b><tt><br>
</tt><tt> 6 45 53 any</tt><tt><br>
</tt><tt> 48 50 53 any</tt><tt><br>
</tt><tt> 53 57 53 any</tt><tt><br>
</tt><br>
catch (OutOfMemoryError | InterruptedException x) variant has the
exactly same bytecodes but the following exception table:<br>
<br>
<tt> Exception table:</tt><tt><br>
</tt><tt> from to target type</tt><tt><br>
</tt><b><tt> 33 39 42 Class
java/lang/OutOfMemoryError</tt></b><b><tt><br>
</tt></b><b><tt> 33 39 42 Class
java/lang/InterruptedException</tt></b><tt><br>
</tt><tt> 6 45 53 any</tt><tt><br>
</tt><tt> 48 50 53 any</tt><tt><br>
</tt><tt> 53 57 53 any</tt><br>
<br>
<br>
... 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).<br>
<br>
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):<br>
<br>
<br>
<tt> try {</tt><tt><br>
</tt><tt> lock.wait();</tt><tt><br>
</tt><tt> } catch (OutOfMemoryError x) { }</tt><tt><br>
</tt><tt> catch (InterruptedException x) {
}</tt><br>
<br>
<br>
...the benefit of this is that OOME is never thrown two times.<br>
<br>
Regards, Peter<br>
<br>
</body>
</html>