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.