RFC 7038914: VM could throw uncaught OOME in ReferenceHandler thread
David Holmes
david.holmes at oracle.com
Fri May 10 23:45:25 UTC 2013
On 11/05/2013 6:53 AM, Dean Long wrote:
> On 5/10/2013 1:22 PM, Peter Levart wrote:
>>
>> 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" :-)
Inflation has already occurred if you do an Object.wait() as in this case.
>>> Aren't monitors C++ objects? Are they allocated from java heap?
>>
>> Ah, I see you're suggesting that "native" allocations can also trigger
>> OOME in Java program. Is this true?
>>
>
> That's what I was suggesting, but in the case of object monitors, it
> looks like the VM will exit if it runs out.
> Other places where we could fail to allocate native memory, such as
> class loading or adding to the system dictionary,
> might not exit the VM, but instead result in some other exception such
> as NoSuchMethod or ClassNotFound.
> I haven't investigated all the possibilities, but I just wanted to bring
> it up as something to consider.
Right - most C-heap allocation failures in the VM result in an abort but
some will convert to OOME (such as native thread creation related failures).
Any test that tries to force an OOME at a particular operation is going
to be fragile because you can't know what other allocations might be
needed under the covers. As Peter already discovered it might occur
during the "new" of InterruptedException, or it might happen during the
load/initialization of InterruptedException. So pre-initializing
InterruptedException is probably a wise thing to do.
Aside we should also run these kind of heap-exhaustion tests with as
small a heap as possible :)
David
-----
> dl
>
>> 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