Let me analyze the execution efficiency of try-catch-finally (TCF) from the perspective of bytecode……

I think it should be the interviewer in the process of the interview to see everyone back of the eight-part essay are the same, feel that there is no need to ask, they turned around to test everyone’s understanding. Today, while brother B is here, we will sum up the knowledge points related to TCF and look forward to a 50-50 discussion with the interviewer next time!

Environment preparation: IntelliJ IDEA 2020.2.3, JDK 1.8.0_181

Execution order

Let’s start with a simple piece of code:

public static int test1() {    int x = 1;    try {        return x;    } finally {        x = 2;    }}
Copy the code

The answer is 1, not 2. Are you right?

In TCF, a finally operation is executed first and then a return is returned. Let’s decompile the bytecode file.

Command: javap -v xxx.class

The bytecode instructions are obscure, so let’s illustrate them graphically (let’s look at the first seven lines) : int x = 1;

And then we need to do return x in the try;

At this time, it does not really return the value of X, but stores the value of X in the local variable table as a temporary storage variable for storage, that is, to protect the value.

Finally x=2;

At this point, x has already been assigned 2, but because of the protection operation, the protected temporary storage variable will be pushed onto the stack when the actual return operation is performed.

To better understand the above operation, let’s write a simple piece of code:

public static int test2() {    int x = 1;    try {        return x;    } finally {        x = 2;        return x;    }}
Copy the code

So let’s think about what is the execution result? The answer is 2, not 1.

Let’s take a look at the bytecode instructions for this program

By comparison, line 6 is iloAD_1 and iloAD_0. What determines this? The reason is that the protection mechanism we mentioned above fails when there is a return statement in finally, and instead pushes the value of the variable onto the stack and returns it.

summary

  • Return has a higher execution priority than finally, but the function does not end immediately after the return statement is executed. Instead, the result is saved to a local variable table in the stack frame, and the statement in the finally block continues execution.

  • If a finally block contains a return statement, the value to be returned in the try block is not protected. Instead, it jumps directly to the finally statement and finally returns the value changed in the finally block.

Why must finally be executed

If you are careful, you will notice that lines 4-7 and 9-12 in the above bytecode instruction diagram are exactly the same, so why are there repeated instructions?

First, let’s analyze what these repeated instructions do. After analysis, we find that they are x = 2; return x; The bytecode instructions in finally code block. It is reasonable to suspect that if a catch block is added to the above code, the bytecode instructions corresponding to the finally block will also appear again.

public static int test2() {    int x = 1;    try {        return x;    } catch(Exception e) {        x = 3;    } finally {        x = 2;        return x;    }}
Copy the code

After decompilation

Sure enough, repeated bytecode instructions appeared three times. Back to the original question, why do the bytecode instructions in finally code repeat themselves three times?

The JVM appended the finally bytecode instructions to try and catch, plus its own instructions, exactly three times to ensure that all exception paths and normal paths were executed in finally. This is why finally must be executed.

Will finally be executed?

Why do you need to do something else when you already said that the code in finally must execute? Please 👇

Under normal circumstances, it must be executed, but there are at least three cases in which it must not be executed:

  • The try statement is returned without being executed, so the finally statement will not be executed. This also means that the finally statement must be executed only if the corresponding try statement is executed.

  • The try block has system.exit (0); Such a statement, because system.exit (0); Finally is not executed when the JVM is stopped; finally is not executed when the JVM is stopped.

  • The daemons exit when all non-daemons exit. The finally code inside the daemons has not been executed, and the non-daemons terminate or exit.

Efficiency of TCF

Speaking of TCF efficiency, we have to introduce the exception table, take the above program for example, after decomcompiling the class file, the exception table information is as follows:

  • From: represents the starting position of the range monitored by the exception handler;

  • To: represents the end position of the range monitored by the exception handler (this line is not included in the monitoring range and is the pre-closed and post-open range);

  • Target: indicates the starting position of the exception handler.

  • Type: represents the type of exception caught by the exception handler;

Each line in the figure represents an exception handler

Workflow:

  1. When an exception is raised, the JVM traverses all the entries in the exception table from top to bottom;

  2. Compares whether the number of rows that raise an exception is in from-to range;

  3. After a range match, it continues to compare whether the type of exception thrown is the same as the type caught by the exception handler;

  4. If the type is the same, it jumps to the number of rows pointed to by target.

  5. If the type is different, a Java stack frame corresponding to the current method will pop up and repeat the operation to the caller;

  6. In the worst case, the JVM needs to traverse the exception table for all methods on the thread’s Java stack;

Take the first action: If the command between lines 2 and 4 (the code in the try block) throws an Exception of type Class Java /lang/Exception, skip to line 8 and start executing.

8: ASTORE_1 means to save the thrown exception object to position 1 in the local variable table

From the point of view of bytecode instructions, TCF execution time is negligible if no exceptions are thrown in the code; If item 6 above occurs during code execution, as the exception table is traversed, more exception instances are built and the stack traces required for the exception are generated. This accesses the stack frames of the current thread one by one, recording various debugging information, including the class name, method name, line of code that raised the exception, and so on. So the execution efficiency will be greatly reduced.

See here, do you have a deeper understanding of TCF? Next time I ask you to interview an online interviewer, will you split it 50/50?

PS: In case you can’t find this article, please click “like” to browse and find it