【问题标题】:Why are some instructions repeated in java bytecode?为什么java字节码中重复一些指令?
【发布时间】:2014-03-23 16:13:48
【问题描述】:

Java 代码如下所示:

File f1 = File.createTempFile("example", ".txt");
File f2 = File.createTempFile("outExample", ".txt");        
FileInputStream in = new FileInputStream(f1);
FileOutputStream out = new FileOutputStream(f2);
try {
    int c;
    while ((c = in.read()) != -1) {
             out.write(c);
    }
} 
finally {
    if (in != null) {
        in.close();
    }
    if (out != null) {
        out.close();
    }
}       
return f1.delete() && f2.delete();

以及为此的字节码:

TRYCATCHBLOCK L0 L1 L1 
   L2
    LINENUMBER 138 L2
    LDC "example"
    LDC ".txt"
    INVOKESTATIC File.createTempFile (String, String) : File
    ASTORE 1
   L3
    LINENUMBER 139 L3
    LDC "outExample"
    LDC ".txt"
    INVOKESTATIC File.createTempFile (String, String) : File
    ASTORE 2
   L4
    LINENUMBER 141 L4
    NEW FileInputStream
    DUP
    ALOAD 1: f1
    INVOKESPECIAL FileInputStream.<init> (File) : void
    ASTORE 3
   L5
    LINENUMBER 142 L5
    NEW FileOutputStream
    DUP
    ALOAD 2: f2
    INVOKESPECIAL FileOutputStream.<init> (File) : void
    ASTORE 4
   L0
    LINENUMBER 146 L0
    GOTO L6
   L7
    LINENUMBER 147 L7
    ALOAD 4: out
    ILOAD 5: c
    INVOKEVIRTUAL FileOutputStream.write (int) : void
   L6
    LINENUMBER 146 L6
    ALOAD 3: in
    INVOKEVIRTUAL FileInputStream.read () : int
    DUP
    ISTORE 5
   L8
    ICONST_M1
    IF_ICMPNE L7
   L9
    LINENUMBER 149 L9
    GOTO L10
   L1
    LINENUMBER 150 L1
    ASTORE 6
   L11
    LINENUMBER 151 L11
    ALOAD 3: in
    IFNULL L12
   L13
    LINENUMBER 152 L13
    ALOAD 3: in
    INVOKEVIRTUAL FileInputStream.close () : void
   L12
    LINENUMBER 154 L12
    ALOAD 4: out
    IFNULL L14
   L15
    LINENUMBER 155 L15
    ALOAD 4: out
    INVOKEVIRTUAL FileOutputStream.close () : void
   L14
    LINENUMBER 157 L14
    ALOAD 6
    ATHROW
   L10
    LINENUMBER 151 L10
    ALOAD 3: in
    IFNULL L16
   L17
    LINENUMBER 152 L17
    ALOAD 3: in
    INVOKEVIRTUAL FileInputStream.close () : void
   L16
    LINENUMBER 154 L16
    ALOAD 4: out
    IFNULL L18
   L19
    LINENUMBER 155 L19
    ALOAD 4: out
    INVOKEVIRTUAL FileOutputStream.close () : void
   L18
    LINENUMBER 159 L18
    ALOAD 1: f1
    INVOKEVIRTUAL File.delete () : boolean
    IFEQ L20
    ALOAD 2: f2
    INVOKEVIRTUAL File.delete () : boolean
    IFEQ L20
    ICONST_1
    IRETURN
   L20
    ICONST_0
    IRETURN

谁能告诉我为什么每个流(L13 和 L17、L15 和 L19)调用两次 close() 指令?如果没有 finally 块,但使用 try-catch,则字节码中的每个流只调用一个 close()。

【问题讨论】:

  • 我看到两个close 电话。
  • @ThomasJungblut 肯定有 4 个。
  • 除此之外:finally-close 模式被打破了。如果第一次关闭抛出异常(不要忘记运行时异常),第二次将永远不会被调用。
  • @SotiriosDelimanolis 有这样的东西:“所以如果语句0-9之间发生任何异常,请转到第20行并在goto之后执行finally中的所有内容。如果没有发生异常,则执行finally 然后执行 goto,在 goto 之后跳过 finally。”在我的情况下没有发生异常,那么为什么 finally 出现两次?也许我没有正确理解。

标签: java bytecode


【解决方案1】:

一个用于隐式捕获异常并需要重新抛出的情况(注意末尾的ATHROW

L15
 LINENUMBER 155 L15
 ALOAD 4: out
 INVOKEVIRTUAL FileOutputStream.close () : void
L14
 LINENUMBER 157 L14
 ALOAD 6
 ATHROW

不抛出异常时的另一个:

L19
 LINENUMBER 155 L19
 ALOAD 4: out
 INVOKEVIRTUAL FileOutputStream.close () : void

如果你看顶部,你会看到

TRYCATCHBLOCK L0 L1 L1 

这些标签类似于在哪里跳到 catch 和在哪里跳到 finally。 Catch 将抛出的异常留在堆栈顶部(同样,ATHROW)。检查堆栈以查看顶部成员是否为异常比仅复制代码要昂贵得多(并且更难/不可能;堆栈没有类型)。

【讨论】:

  • 谢谢,大卫。当我使用 ASM 框架时,我能以任何方式区分这些调用吗?
  • @marcin 看看TRYCATCHBLOCK L0 L1 L1。我认为第一个参数是你去哪里开始block,第二个是block成功结束时去哪里,第三个是出现异常时去哪里。还有另一种形式的 TRYCATCHBLOCK 可以使用单个异常类型,然后将它们链接在一起。你的头痛最终是知道何时完成捕获。为此,它要么是重新投掷,要么你已经达到了可以从捕获之外访问的标签。这很痛苦……除非您查看行号和来源。
  • @marcin 实际上,你最好用自己的标签重写TRYCATCHBLOCK 来拦截它们,然后跳转到原来的标签。
猜你喜欢
  • 2018-06-09
  • 2020-10-27
  • 2014-05-05
  • 1970-01-01
  • 2015-05-03
  • 1970-01-01
  • 2021-04-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多