【问题标题】:Unexpected code in synchronized block同步块中的意外代码
【发布时间】:2012-03-31 14:28:22
【问题描述】:

以下 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

【问题讨论】:

    标签: java jvm jls


    【解决方案1】:

    编译器在此处添加了一个不可见的 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 编程语言的编译器可确保 执行的 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,您的回答没有回答以下问题:“我很好奇为什么会生成代码……”甚至是“我错过了什么吗?”。不过感谢您的参与。
      • 生成代码是因为必须有代码才能释放锁,请参见我的示例。你错过了我解释的尝试。 ;)
      • Rene 的解释更好。另外,请注意,我没有要求其他方式。无论如何,谢谢。
      • 这是为了澄清到底发生了什么。我假设您可能更熟悉 java 代码而不是字节代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-20
      • 2020-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多