RFC 7038914: VM could throw uncaught OOME in ReferenceHandler thread

David Holmes david.holmes at oracle.com
Fri May 10 13:14:03 UTC 2013


Hi Peter,

So it would appear that it is not in fact the "new" that causes the OOME 
but the classloading of InterruptedException ?

I'm not sure I can quite get my head around this late on a Friday night :)

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 hotspot-gc-dev mailing list