How does finally work in java?


There is a code:

private static int f (){
    try {
        return 1;
    }
    finally {
        return 2;
    }
}

public static final void main(String[] args) {
    System.out.println(f());
}

OUTPUT: 2

It seems self-evident that after the compiler comes across return 1, it should finish the method, and output one, because the method is executed line by line. But the compiler behaves somehow strangely, and instead of returning with a unit, it falls into the section finally.

Question: And how does the compiler actually " see " the code? Maybe it treats the try block as a call from the finally block? Like this:

int finallyCompilator(){
int tryCompilator(); 
return 2; //Здесь код метода finally
}

tryCompilator(){
return 1;  //Здесь код метода try
}

But even if so, it is not clear how the compiler will "see" the catch block, if there is one?

Author: Kromster, 2017-10-11

3 answers

Everything is much simpler, there is a substitution of the return block

To see this in practice, compile your code in bytecode and open it for example through Intellij IDEA and see the following picture:

public class Main {
    public Main() {
    }

    private static int f() {
        try {
            boolean var0 = true;
            return 2;
        } finally {
            ;
        }
    }

    public static final void main(String[] var0) {
        System.out.println(f());
    }
}

We can notice that during compilation, the return block from try was replaced with return from finally

UPD:

Let's make the situation more interesting and compile such a function

private static int f (){
    try {
        System.exit(0);
        return 1;
    }
    finally {
        System.out.println("smth");
        return 2;
    }
}

Her bytecode will be like this:

private static int f() {
    try {
        System.exit(0);
        boolean var0 = true;
    } finally {
        System.out.println("smth");
        return 2;
    }
}

Here we see that return is simply deleted from the try block, and System.exit(0) will naturally throw it out of the application faster than the finally block will work.

 12
Author: Komdosh, 2017-10-11 10:06:18

Short answer: the compiler copies the finally block before each return and into each catch block. Read more below.

The question why finally should be called after return is answered in the question: Is finally executed if in try return?

As for the implementation, in addition to @Komdosh's research, I tried looking for the finally compilation requirements in the Java Virtual Machine specification. Chapter 3.13 describes the compilation mechanism finally using the JSR and RET instructions. The instructions and implementation examples are outdated, but you can roughly understand the standard finally compilation mechanism from the description:

The jsr instruction pushes the address of the following instruction (return at index 7) onto the operand stack before jumping. The astore_2 instruction that is the jump target stores the address on the operand stack into local variable 2. The code for the finally block (in this case the aload_0 and invokevirtual instructions) is run. Assuming execution of that code completes normally, the ret instruction retrieves the address from local variable 2 and resumes execution at that address. The return instruction is executed, and tryFinally returns normally.

A try statement with a finally clause is compiled to have a special exception handler, one that can handle any exception thrown within the try statement. If tryItOut throws an exception, the exception table for tryFinally is searched for an appropriate exception handler. The special handler is found, causing execution to continue at index 8. The astore_1 instruction at index 8 stores the thrown value into local variable 1. The following jsr instruction does a subroutine call to the code for the finally block. Assuming that code returns normally, the aload_1 instruction at index 12 pushes the thrown value back onto the operand stack, and the following athrow instruction rethrows the value.

Accordingly, the interpretation of finally in the try-catch-finally block should be equivalent to the following:

  • the entire block is wrapped by a special exception handler, which stores the exception not handled by catch (if any) into a local variable, performs a jump / jump (JSR) to the finally block, then throws the exception;
  • before each return, the transition to the finally block is performed.

Instructions JSR used previously to save instructions for finally blocks. But class files starting from version 51 (Java 7) do not support the instruction. The JSR documentation says that the instruction was used before version 6 to support finally

In Oracle's implementation of a compiler for the Java programming language prior to Java SE 6, the jsr instruction was used with the ret instruction in the implementation of the finally clause.

From a specific version The Sun compiler, then Oracle, instead of using transitions, began copying and embedding finally blocks, which eliminated the need for instructions.

Given the above, the following code block:

try {
    if(isSomething()) {
         return foo();
    }
    return bar(); 
} catch(FooException e) {
    handle(e);
} finally {
    /** блок finally **/
}

, the compiler converts to a block equivalent to the following:

try {
    if(isSomething()) {
        T temp1 = foo();
        /* копия блока finally */
        return temp1;
    }
    T temp2 = bar();
    /* копия блока finally */
    return temp2; 
} catch(FooException e) {
    handle(e);
    /* копия блока finally */
} catch(Throwable t) {
    /* копия блока finally */
    throw t;
}

Checking the bytecode generated by javac for Oracle JDK 8 confirms that the finally block is copied several times (invokevirtual #10 is a method that is called only in the finally block, complete code):

Code:
   0: aload_0
   1: invokevirtual #8                  // Method isSomething:()Z
   4: ifeq          17
   7: aload_0
   8: invokevirtual #9                  // Method foo:()I
  11: istore_1
  12: aload_0
  13: invokevirtual #10                 // Копия finally 1
  16: ireturn
  17: aload_0
  18: invokevirtual #11                 // Method bar:()I
  21: istore_1
  22: aload_0
  23: invokevirtual #10                 // Копия finally 2
  26: ireturn
  27: astore_1
  28: aload_0
  29: aload_1
  30: invokevirtual #13                 // Method handle:(LMain$FooException;)V
  33: aload_0
  34: invokevirtual #10                 // Копия finally 3
  37: ireturn
  38: astore_2
  39: aload_0
  40: invokevirtual #10                 // Копия finally 4
  43: ireturn

As a result of optimization, the compiler can reduce some of the instructions, but the approach should not change dramatically.

It is worth noting once again that I did not find in the specification strict requirements for the bytecode that the compiler should generate for finally. As far as I understand, another compiler implementation could use the goto statement for these purposes, although the gain would be insignificant. Special constructs for finally at the bytecode level in modern versions of Java, no.

Relevant links :

 9
Author: default locale, 2020-06-12 12:52:24

The finally block is always executed. Exception

try { System.exit(0); }

"Maybe it perceives the try block as a call from the finally " block? No, just finally is executed after try, unless an exception occurred. If it happened, then catch, then finally.

 0
Author: HasmikGaryaka, 2017-10-11 09:59:18