RFC 7038914: VM could throw uncaught OOME in ReferenceHandler thread

Peter Levart peter.levart at gmail.com
Fri May 10 20:22:48 UTC 2013


On 05/10/2013 10:08 PM, Peter Levart wrote:
>
> On 05/10/2013 08:10 PM, Dean Long wrote:
>> If you really want to bullet-proof ReferenceHandler (and other system 
>> threads) against OOME caused by native allocations,
>> then don't forget about monitor inflation if there is contention for 
>> "lock" :-)
>
> Aren't monitors C++ objects? Are they allocated from java heap?
>
> Regards, Peter

Hi Dean,

Ah, I see you're suggesting that "native" allocations can also trigger 
OOME in Java program. Is this true?

Regards, Peter

>
>>
>> dl
>>
>> On 5/10/2013 6:14 AM, 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 ?
>>>
>>> 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 core-libs-dev mailing list