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