简体   繁体   English

Java:当在 catch 块中抛出 InterruptedException 时,finally-block 似乎被执行了两次

[英]Java: finally-block seems to be executed twice when InterruptedException is thrown in catch block

In the following code:在以下代码中:

void foo() throws InterruptedException {
    try {
        f1();
    }
    catch (InterruptedException e) {
        f2();
        throw e;
    }
    finally {
        f3(); //breakpoint hit twice
    }
}

When InterruptedException is thrown by f1() , the breakpoint in the finally block is hit twice, but f3() is entered only once while debugging.f1()抛出InterruptedException时, finally块中的断点被击中两次,但调试时f3()仅进入一次。 I'm wondering if this is normal.我想知道这是否正常。

This is possibly related to the way try/finally is contemporarily turned into bytecode (as in, since.. JDK... 6? It's been a long long time, but in the distant past, opcodes JMP and RET were used for this; those opcodes are no longer emitted by javac and haven't been for a very very long time).这可能与 try/finally 被同时转换为字节码的方式有关(例如,自从.. JDK... 6?已经很久很久了,但在遥远的过去,操作码 JMP 和 RET 被用于此;这些操作码不再由javac发出并且已经很长时间没有发出了)。

The finally block is just repeated. finally 块只是重复。 Loads of times, if needed.很多次,如果需要的话。 java translates it to, basically, 'catch anything, run the finally block, then rethrow whatever you caught', + '... and duplicate this code at the end of the body of the try block', + '... and duplicate this code at the end of each and every catch block'. java 基本上将其翻译为“捕捉任何东西,运行 finally 块,然后重新抛出你捕捉到的任何东西”,+“......并在 try 块主体的末尾复制这段代码”,+“......和在每个 catch 块的末尾复制此代码'。

Thus, in your example code, your finally block is actually present in your compiled bytecode 3 times .因此,在您的示例代码中,您的finally块实际上出现在您编译的字节码中3 次 Your code is syntax desugared first from:您的代码是语法脱糖首先来自:

   try {
        f1();
    }
    catch (InterruptedException e) {
        f2();
        throw e;
    }
    finally {
        f3(); //breakpoint hit twice
    }

to:到:

   try {
        f1();
        f3(); // CAVEAT 1
    }
    catch (InterruptedException e) {
        try {
          f2();
          f3(); // CAVEAT 2
        } catch (Throwable t) {
          f3();
          throw t;
        }
    }
    catch (Throwable t) {
        f3();
        throw t;
    }

CAVEAT1: Though, with additional bookkeeping that f3() isn't re-invoked in response to throwing an exception itself. CAVEAT1:虽然,通过额外的簿记, f3()不会重新调用以响应抛出异常本身。 In java code that's hard to easily write, in bytecode its trivial - try/catch blocks work by declaring 'this range of opcodes?在 java 代码中很难轻松编写,在字节码中它是微不足道的 - try/catch 块通过声明'这个操作码范围? Jump to this code if exceptions occur'.如果发生异常则跳转到此代码'。 The f3() duplication in the try block simply isn't part of the range. try 块中的f3()重复项根本不在范围内。

CAVEAT2: The bytecode generated is efficient enough to only have the finally body once per catch block. CAVEAT2:生成的字节码足够高效,每个 catch 块只有一次 finally 主体。

Hence, you have many different bytecode in your class file that all has the same 'line number'.因此,您的 class 文件中有许多不同的字节码,它们都具有相同的“行号”。 Debugging fundamentally occurs in bytecode (eg when you set a breakpoint, you pick a line. However, some system needs to translate that line to an actual bytecode, and breakpoint that bytecode. In this case, that's tricky: The one line you breakpoint actually has 3 completely different bytecode items. Presumably, your debugger adds breakpoint hooks to all of them .调试从根本上发生在字节码中(例如,当您设置断点时,您选择一行。但是,某些系统需要将该行转换为实际的字节码,并在该字节码处设置断点。在这种情况下,这很棘手:您断点的那一行实际上有 3 个完全不同的字节码项目。据推测,您的调试器为所有这些项目添加了断点挂钩。

Just guessing, but it's easy to say how this leads to accidental double firing.只是猜测,但很容易说出这是如何导致意外连发的。 It shouldn't - your debugger is clearly itself buggy (heh), but if you want to figure out why its happening, or even help out and write a PR to fix the problem, this is where I'd start.它不应该——你的调试器显然本身就有问题(呵呵),但如果你想弄清楚为什么会这样,或者甚至想帮忙写一个 PR 来解决这个问题,这就是我要开始的地方。

You can check this 'whaaaa?你可以检查这个'whaaaa? Are finally blocks duplicated this much? finally块是否重复了这么多? Crazy!'疯狂的!' stuff by using javap -c , which shows you bytecode.通过使用javap -c来显示字节码。

For example:例如:

class Test { void foo() {
    try {
        System.out.println("A");
    } catch (NullPointerException e) {
        System.out.println("B");
    } finally {
        System.out.println("C");
    }
}}

Then javac Test.java; javap -c Test然后javac Test.java; javap -c Test javac Test.java; javap -c Test shows: javac Test.java; javap -c Test显示:

void foo();
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #13                 // String A
       5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: ldc           #21                 // String C
      13: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      16: goto          50
      19: astore_1
      20: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: ldc           #25                 // String B
      25: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      28: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: ldc           #21                 // String C
      33: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      36: goto          50
      39: astore_2
      40: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      43: ldc           #21                 // String C
      45: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      48: aload_2
      49: athrow
      50: return
    Exception table:
       from    to  target type
           0     8    19   Class java/lang/NullPointerException
           0     8    39   any
          19    28    39   any

breaking that down: You can easily see how C is loaded three times, but A and B only once, which is bizarre given that C is only mentioned once in the source code - until you realize that finally blocks are duplicated all over the place.分解一下:你可以很容易地看到C如何被加载三次,但AB只加载一次,这很奇怪,因为 C 在源代码中只被提及一次 - 直到你意识到finally块在所有地方都是重复的。

If you throw an exception in a method by stating it throws the exception, then you do not handle it inside the method.如果你通过声明它抛出异常来在方法中抛出异常,那么你不会在方法内部处理它。

Actually the code you shared has lots of mistakes that I can't show my example, so I'll show my example.实际上你分享的代码有很多错误我无法展示我的例子,所以我会展示我的例子。

Lets say you have a method called foo() that throws an exception假设您有一个名为 foo() 的方法会抛出异常

int foo(int a, int b) throws DivisionByZero{
    if (b == 0){
        DivisionByZero myException = new DivisionByZero();
        throw myException;
    }
    else
        return a/b;
}

Now, if you want to use foo() method in a foo2() method, then you either declare the foo2() as throws Exception DivisionByZero or you should handle it inside the foo2() method.现在,如果你想在foo2()方法中使用foo()方法,那么你要么将foo2()声明为throws Exception DivisionByZero ,要么你应该在foo2()方法中处理它。 DO NOT DO BOTH不要两者都做

Like this:像这样:

foo2(int a, int b) throws DivisionByZero{
    int result = foo(a,b);
    System.out.println(result);
}

or this:或这个:

foo2(int a, int b){
    int result;
    try{
        result = foo(a,b);
        System.out.println("No Exception.");
    }catch(DivisionByZero e){
        System.out.println("Caught.");
    }finally{
        System.out.println("I'm done."); //works once either completing try or catch.
    }        
    System.out.println(result);
}

output of this last foo2() code is either最后一个 foo2() 代码的 output 是

No Exception.没有例外。

I'm done.我受够了。

or或者

Caught.捕捉。

I'm done.我受够了。

0 //because int type initialized to zero. 0 //因为int类型初始化为零。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM