同步块中出现意外代码

7
以下Java代码生成以下JVM字节码。
我想知道为什么会生成31到36偏移量的代码。JLS7或JVM7规范中没有讨论这个问题。我错过了什么吗?
即使我删除println语句,31到36偏移量的代码仍然会被生成,只是在较早的位置,因为println调用已被删除。
// Java code
    void testMonitor() {
        Boolean x = new Boolean(false);
        synchronized(x) {
            System.out.println("inside synchronized");
            System.out.println("blah");
        };
        System.out.println("done");
    }


// JVM bytecode
    Offset  Instruction       Comments (Method: testMonitor)
    0       new 42            (java.lang.Boolean)
    3       dup
    4       iconst_0
    5       invokespecial 44  (java.lang.Boolean.<init>)
    8       astore_1          (java.lang.Boolean x)
    9       aload_1           (java.lang.Boolean x)
    10      dup
    11      astore_2
    12      monitorenter
    13      getstatic 15      (java.lang.System.out)
    16      ldc 47            (inside synchronized)
    18      invokevirtual 23  (java.io.PrintStream.println)
    21      getstatic 15      (java.lang.System.out)
    24      ldc 49            (blah)
    26      invokevirtual 23  (java.io.PrintStream.println)
    29      aload_2
    30      monitorexit
    31      goto 37
    34      aload_2
    35      monitorexit
    36      athrow
    37      getstatic 15      (java.lang.System.out)
    40      ldc 51            (done)
    42      invokevirtual 23  (java.io.PrintStream.println)
    45      return
2个回答

7

编译器在这里添加了一个隐形的try/catch块,以确保监视器状态得到释放(这在VM规范中有记录,请参见帖子底部)。您可以使用javap -v来验证此操作,并查看异常表:

void testMonitor();
  Code:
   Stack=3, Locals=3, Args_size=1
   0:    new    #15; //class java/lang/Boolean
   3:    dup
   4:    iconst_0
   5:    invokespecial    #17; //Method java/lang/Boolean."<init>":(Z)V
   8:    astore_1
   9:    aload_1
   10:    dup
   11:    astore_2
   12:    monitorenter
   13:    getstatic    #20; //Field java/lang/System.out:Ljava/io/PrintStream;
   16:    ldc    #26; //String inside synchronized
   18:    invokevirtual    #28; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   21:    getstatic    #20; //Field java/lang/System.out:Ljava/io/PrintStream;
   24:    ldc    #34; //String blah
   26:    invokevirtual    #28; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   29:    aload_2
   30:    monitorexit
   31:    goto    37
   34:    aload_2
   35:    monitorexit
   36:    athrow
   37:    getstatic    #20; //Field java/lang/System.out:Ljava/io/PrintStream;
   40:    ldc    #36; //String done
   42:    invokevirtual    #28; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   45:    return
  Exception table:
   from   to  target type
    13    31    34   any
    34    36    34   any

编辑:根据JVM规范

通常,Java编程语言的编译器会确保,在执行同步语句体之前执行的monitorenter指令实现的锁操作,与在同步语句完成时无论是正常还是异常情况下执行的monitorexit指令实现的解锁操作相匹配。


2
我不知道在JLS中它在哪里,但一定有一个地方说当抛出异常时,锁会被释放。您可以使用Unsafe.monitorEnter/Exit来实现此功能。
void testMonitor() {
    Boolean x = new Boolean(false);
    theUnsafe.monitorEnter(x);
    try {
        System.out.println("inside synchronized");
        System.out.println("blah");
    } catch(Throwable t) {
        theUnsafe.monitorExit(x);
        throw t;
    };
    theUnsafe.monitorExit(x);
    System.out.println("done");
}

我相信你可能遗漏了最后的catch块表。

你好 Peter,你的回答并没有回答问题:“我很好奇为什么会生成这段代码”,甚至是“我有什么遗漏吗?”感谢你的参与。 - chuacw
代码是生成的,因为必须有代码来释放锁定,可以看看我的示例。你错过了我试图解释的部分。;) - Peter Lawrey
Rene的解释更好。另外,注意我并没有要求提供其他替代方法。无论如何,还是谢谢。 - chuacw
这是为了澄清正在发生的事情。我认为你可能比字节码更熟悉Java代码。 - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接