同步块中的意外代码

时间:2012-03-31 14:28:23

标签: java jvm jls

以下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 个答案:

答案 0 :(得分: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 specs

  

通常,Java编程语言的编译器可以确保这一点   由执行的监视器指令实现的锁定操作   在执行synchronized语句的主体之前是   由monitorexit实现的解锁操作匹配   是否同步语句完成时的指令   完成是正常的或突然的。

答案 1 :(得分: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块表。