Hello everyone, I’m Ah Q!

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(a) {
    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(a) {
    int x = 1;
    try {
        return x;
    } finally {
        x = 2;
        returnx; }}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

  • returnThe execution priority of thefinallyThe execution priority of, butreturnInstead of terminating the function immediately after the statement completes, the result is saved toThe stack frameIn theLocal variable scale“, and continuefinallyA statement in a block;
  • iffinallyBlock containsreturnStatement, does nottryBlock to return the value to be protected, instead jumping directly tofinallyStatement, and finally infinallyStatement, the return value is in thefinallyThe value changed in the 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(a) {
    int x = 1;
    try {
        return x;
    } catch(Exception e) {
        x = 3;
    } finally {
        x = 2;
        returnx; }}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:

  • tryThe statement is returned without being executedfinallyThe statement will not execute, so that showsfinallyA necessary but not sufficient condition for a statement to be executed is: correspondingtryStatement must be executed to;
  • tryIn the code blockSystem.exit(0);Such a statement, becauseSystem.exit(0);Is terminatedJVM, evenJVMIt’s all stopped,finallyIt certainly won’t be carried out;
  • Daemon threads exit when all non-daemon threads exitDaemon threadThe inside of thefinallyWhen the non-daemon thread terminates or exits,finallyCertainly not enforced;

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 triggered,JVMAll entries in the exception table are traversed from top to bottom;
  2. Compares whether the number of rows that trigger an exception is infromtoWithin the scope;
  3. After a range match, comparisons continue between the types of exceptions thrown and those caught by the exception handlertypeWhether the same;
  4. If the type is the same, jump totargetThe number of rows pointed to starts executing;
  5. If the type is different, the corresponding to the current method will be displayedjavaStack frame, and repeat the operation to the caller;
  6. Worst-case scenarioJVMThe thread needs to be traversedJavaException table for all methods on the 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?

Ah Q will continue to update the Java practice of the article, interested in the public account can pay attention to: AH Q said the code, also can come to the technical group to discuss the problem!