Analysis of Java exception implementation principle from the perspective of bytecode

1, return and throw in Java exception handling

If you put return and throw together, the error will be prompted directly. "Unreachable statement"

However, finally, we can successfully cheat the compiler to let them coexist (is it a minor bug of the compiler), and the result is that the latter will overwrite the former.

Finally, if there is a return, it will overwrite the throw in the catch. Similarly, if there is a throw in finally, it will overwrite the return in the catch. Furthermore, if there is return finally in both catch and finally, the return in catch will be overwritten. So is throw.

In this way, it's easy to understand that both rerun and throw make the program jump out of the current method, which is naturally a conflict. If you have to jump out twice, the latter will cover the former.

Example:

public class T {
    public T() {
    }
 
    boolean testEx() throws Exception {
        boolean ret = true;
        try {
            ret = testEx1();
        } catch (Exception e) {
            System.out.println("testEx, catch exception");
            ret = false;
            throw e;
        } finally {
            System.out.println("testEx, finally; return value=" + ret);
            return ret;
        }
    }
 
    boolean testEx1() throws Exception {
        boolean ret = true;
        try {
            ret = testEx2();
            if (!ret) {
                return false;
            }
            System.out.println("testEx1, at the end of try");
            return ret;
        } catch (Exception e) {
            System.out.println("testEx1, catch exception");
            ret = false;
            throw e;
        }
        finally {
            System.out.println("testEx1, finally; return value=" + ret);
            return ret;
        }
    }
 
    boolean testEx2() throws Exception {
        boolean ret = true;
        try {
            int b = 12;
            int c;
            for (int i = 2; i >= -2; i--) {
                c = b / i;
                System.out.println("i=" + i);
            }
           return true;
        } catch (Exception e) {
            System.out.println("testEx2, catch exception");
            ret = false;
            throw e;
        }
        finally {
            System.out.println("testEx2, finally; return value=" + ret);
            //return ret;
        }
    }
 
    public static void main(String[] args) { 
        T testException1 = new T();
        try {
            testException1.testEx();
        } catch (Exception e) {
            e.printStackTrace();
        }
    } 
}

Console output:

i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, catch exception
testEx1, finally; return value=false
testEx, finally; return value=false

The execution process of try catch finally block:

The statement in the try statement block is executed first. There are three possible situations:

1. If all statements in the try block are executed normally, the resident of the finally block will be executed. At this time, there are two situations:

1.1 if the finally block is executed successfully, the whole try catch finally block completes normally.

1.2 if the finally block is terminated suddenly due to reason R, then the result of try catch finally block is "complete aborted due to reason R"

2. If the try statement block encounters exception V during execution, it can be handled in two ways:

2.1 if the exception V can be caught by the catch block corresponding to the try, the catch block from the first catch to the exception (also the catch block matching the exception V closest to the try) will be executed. At this time, there will be two execution results:

2.1.1 if the catch block executes normally, the finally block will be executed, which is divided into two situations:

(1) if the finally block is executed successfully, the whole try catch finally block completes normally.

(2) if the finally block is terminated suddenly due to reason R, then the result of try catch finally block is "complete aborted due to reason R"

2.1.2 if the catch block stops suddenly due to the reason R, then the finally module will be executed, which can be divided into two situations:

(1) if the finally block is executed successfully, the result of the whole try catch finally block is "complete aborted due to reason".

(2) if the finally block is terminated suddenly due to reason S, the result of the whole try catch finally block is "complete aborted due to reason S", and reason R will be discarded.

Although we use throw e in testex2 to throw an exception, because there is a finally block in testex2, the execution result of the finally block is complete abruptly. Because return is also one of the reasons leading to complete abruptly, so the result of the whole try-catch-finally block is "complete abruptly", so when testEx2 is invoked in testEx1, it is unable to catch the exception thrown in testEx1, and can only get the return results in finally. Of course, this situation can be avoided. Take testex2 for example: if you must use finally and catch the throw e in the catch in testex1, you can remove the return in finally in testex2.

If the

finally {
            System.out.println("testEx2, finally; return value=" + ret);
            //return ret;
}

Revised to:

finally {
            System.out.println("testEx2, finally; return value=" + ret);
            return ret;
}

Program running results:

i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false

return in try catch finally block

From the above section of the execution process and results of try catch finally block, it can be seen that no matter what happens in try or catch, finally will be executed. Then the return statement written in try or catch will not really jump out of the function. In this case, its function becomes to transfer control (statement flow) to finally block In this case, pay attention to the processing of return value.

For example, if you return false in try or catch and return true in finally, you should not expect the return value of false in your try or catch to be obtained by the superior calling function. The superior calling function can only obtain the return value in finally, because the return statement in try or catch only transfers control. Simply put: the return statement in finally will overwrite the return statement in try or catch statement

2, Implementation of exceptions at bytecode level

Example:

public class ExceptionParse {
    public static void main(String[] args) {
        int a,b,c,d;
        try {
            a = 6;
        } catch (Exception e) {
            b = 7;
        }finally{
            c = 8;
        }
        d = 9;
    }
}

Decompile:

Classfile /D:/java/workspace/JDK/bin/com/lic/test/ExceptionParse.class
  Last modified 2020-1-29; size 698 bytes
  MD5 checksum 265e314f713616a5b32c5b7206658ec5
  Compiled from "ExceptionParse.java"
public class com.lic.test.ExceptionParse
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
{
  public com.lic.test.ExceptionParse();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/lic/test/ExceptionParse;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=7, args_size=1
         0: bipush        6   //Push an 8-bit signed integer (6) onto the stack
         2: istore_1          //Store int type value (6) in local variable 1(a) 
         3: goto          25  //Jump to instruction line No. 25, continue
         6: astore        5   //The value at the top of the stack is stored in the variable at the specified subscript 5 in the local variable array
         8: bipush        7   //Push an 8-bit signed integer (7) onto the stack
        10: istore_2          //Store int type value (7) into local variable 2(b) 
        11: bipush        8   //Push an 8-bit signed integer (8) onto the stack
        13: istore_3          //Store int type value (8) in local variable 3(c)
        14: goto          28  //Jump to line number 28, continue
        17: astore        6   //The value at the top of the stack is stored in the variable at the specified subscript 6 in the local variable array
        19: bipush        8   //Push an 8-bit signed integer (8) onto the stack
        21: istore_3          //Store int type value (8) in local variable 3(c) 
        22: aload         6   //Load reference type values from local variable 6 
        24: athrow            //Throw exception
        25: bipush        8   //Push an 8-bit signed integer (8) onto the stack
        27: istore_3          //Store int type value (8) in local variable 3(c) 
        28: bipush        9   //Push an 8-bit signed integer (9) onto the stack
        30: istore        4   //Store int type value (9) in local variable 4(d) 
        32: return
      Exception table:                   //Exception table
         from    to  target type         //from: start position of exception capture, i.e. start position of try to: end position of exception capture (excluding current position) target: start position of exception processor, i.e. start position of catch type: exception type   
             0     3     6   Class java/lang/Exception    //Exception capture range is instruction line No. [0,3], once the exception is caught, jump to 6 instruction line No. to continue execution
             0    11    17   any
      LineNumberTable:
        line 8: 0
        line 9: 3
        line 10: 8
        line 12: 11
        line 11: 17
        line 12: 19
        line 13: 22
        line 12: 25
        line 14: 28
        line 15: 32
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      33     0  args   [Ljava/lang/String;
           11       6     2     b   I
           14       3     3     c   I
           22       3     3     c   I
           28       5     3     c   I
           32       1     4     d   I
            8       3     5     e   Ljava/lang/Exception;
      StackMapTable: number_of_entries = 4
        frame_type = 70 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]
        frame_type = 74 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 7 /* same */
        frame_type = 255 /* full_frame */
          offset_delta = 2
          locals = [ class "[Ljava/lang/String;", top, top, int ]
          stack = []
}
SourceFile: "ExceptionParse.java"

Program instruction analysis:

Exception list:

Exception table:                  
         from    to  target type       
             0     3     6   Class java/lang/Exception   
             0    11    17   any

from: Exception capture start location,Namely try Start position of  

to: End of exception capture(Exclude current location)

target: Start of exception handler, Namely catch Start position of  

type: Exception type, anyRepresents all types

 0     3     6 : Exception capture range is instruction line number[0,3), Once an exception is caught, jump to line number 6 to continue execution
  

1. If there is no exception in the instruction range [0,3], jump to instruction block 4,5 to continue execution after the execution of instruction block 1, and instruction block 4 is the program instruction in the finally statement block;

2. If there is an exception in the instruction range [0,3], according to the exception table, the program immediately jumps to the instruction line 6 to start execution, that is, the execution instruction block 2, which contains the program instructions in the catch statement block and finally statement block; after the execution of the statement block 2, it jumps to the instruction line 28 to start execution, that is, "d = 9;" and returns after execution

3. If there is an exception in the instruction range [0,11] during program execution, and the exception cannot be caught by the defined exception, that is, unexpected exception occurs in try catch; then according to the exception table, the program immediately jumps to instruction line 17 to start execution, that is, execution instruction block 3. In instruction block 3, it mainly executes the content of finally statement block; this operation is mainly to ensure that In any case, the finally statement block can be executed to; after the execution of instruction block 3, an exception is thrown, the program is terminated, and execution is no longer continued

Exception handling process:

When a program triggers an exception, the Java virtual machine traverses all the entries in the exception table (that is, all the code in try). If the exception finds the bytecode entry where the exception occurred, it will match the exception to be caught by the catch. If it does, the code in the catch will be executed.
If there is no match, it will pop up the Java stack frame corresponding to the current method, and repeat the above operations in the caller. In the worst case, the Java virtual machine needs to traverse the exception table of all methods on the Java stack of the current thread
If an exception occurs in catch, the Java virtual opportunity discards the first exception and tries to catch and handle the new exception. This is not good for debugging in coding.

Reference Podcast: Java exception details - view exception implementation principle from bytecode Perspective

                 Deep understanding of java exception handling mechanism

 

 

83 original articles published, 93 praised, 10000 visitors+
Private letter follow

Tags: Java JDK

Posted on Tue, 28 Jan 2020 20:46:03 -0800 by Jonah Bron