RFC 7038914: VM could throw uncaught OOME in ReferenceHandler thread
Peter Levart
peter.levart at gmail.com
Fri May 10 13:18:55 UTC 2013
On 05/10/2013 03:14 PM, David Holmes wrote:
> Hi Peter,
>
> So it would appear that it is not in fact the "new" that causes the
> OOME but the classloading of InterruptedException ?
Depends on whether the InterruptedException class was already loaded or
not when "new" is attempted.
>
> I'm not sure I can quite get my head around this late on a Friday
> night :)
It will be clear on Monday morning ;)
Peter
>
> David
>
> On 10/05/2013 9:21 PM, Peter Levart wrote:
>> 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
>>
More information about the core-libs-dev
mailing list