RFC 7038914: VM could throw uncaught OOME in ReferenceHandler thread

Peter Levart peter.levart at gmail.com
Fri May 10 13:15:12 UTC 2013


Hi again,

While the below patch works and keeps Reference Handler running in all 
scenarios, the evaluation exposed a weakness of JVM. If loading of a 
class fails because of OOME, this class can not be used in this 
incarnation of the VM even if OOME was just a transient condition.

Regards, Peter

On 05/10/2013 01: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
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/hotspot-gc-dev/attachments/20130510/0484a3fb/attachment.htm>


More information about the hotspot-gc-dev mailing list