Home > front end >  Java: finally-block seems to be executed twice when InterruptedException is thrown in catch block
Java: finally-block seems to be executed twice when InterruptedException is thrown in catch block

Time:05-10

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. I'm wondering if this is normal.

CodePudding user response:

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).

The finally block is just repeated. 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'.

Thus, in your example code, your finally block is actually present in your compiled bytecode 3 times. 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. In java code that's hard to easily write, in bytecode its trivial - try/catch blocks work by declaring 'this range of opcodes? Jump to this code if exceptions occur'. The f3() duplication in the try block simply isn't part of the range.

CAVEAT2: The bytecode generated is efficient enough to only have the finally body once per catch block.

Hence, you have many different bytecode in your class file that all has the same 'line number'. Debugging fundamentally occurs in bytecode (e.g. 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.

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.

You can check this 'whaaaa? Are finally blocks duplicated this much? Crazy!' stuff by using javap -c, which shows you bytecode.

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 shows:

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.

CodePudding user response:

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

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. 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

No Exception.

I'm done.

or

Caught.

I'm done.

0 //because int type initialized to zero.

  • Related