RFC 7038914: VM could throw uncaught OOME in ReferenceHandler thread

Peter Levart peter.levart at gmail.com
Fri May 10 20:08:28 UTC 2013


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

>
> 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