RFC 7038914: VM could throw uncaught OOME in ReferenceHandler thread

Peter Levart peter.levart at gmail.com
Fri May 10 11:21:35 UTC 2013


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/147422c3/attachment.htm>


More information about the hotspot-gc-dev mailing list