try catch 底层原理

有这样一段程序

public class Main {
	public static void main(String[] args) {
		new Main().call();
	}
	public int call() {
		try {
			int a = 1 / 0;
		} catch (Exception e) {
			System.err.println("exception");
			return 0;
		} finally {
			return 4;
		}
	}
}

程序运行结果

java.lang.ArithmeticException: / by zero
4

我们来看下这段代码编译出来的部分字节码:

   public int call();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: iconst_0
         2: idiv
         3: istore_1
         4: iconst_4
         5: ireturn
         6: astore_1
         7: getstatic     #2                  // Field java/lang/System.err:Ljava/io/PrintStream;
        10: aload_1
        11: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        14: iconst_0
        15: istore_2
        16: iconst_4
        17: ireturn
        18: astore_3
        19: iconst_4
        20: ireturn
      Exception table:
         from    to  target type
             0     4     6   Class java/lang/Exception
             0     4    18   any
             6    16    18   any

可以看到,末尾的 Exceptions Table 就是异常表。异常表中的每一条记录,都代表了一个异常处理器。

  • from 可能发生异常的起始点。
  • to 可能发生异常的结束点。
  • target 上述from和to之前发生异常后的异常处理者的位置。
  • type 异常处理者处理的异常的类信息。

比如上面Exceptions Table ,在0-4位置,代表可能发生异常的位置,0-4位置的指令为:

 0: iconst_1   // 将int型常量值1进栈
 1: iconst_0   // int型常量值0进栈
 2: idiv       // 除
 3: istore_1    //将栈顶int型数值存入第二个局部变量,从0开始计数 
 4: iconst_4

就是 int a =1/0; 代码。然后target 为6 ,表示异常后从6位置开始执行...

JVM指令大全

罗政:JVM - 指令集 大全

异常处理流程

  • 当一个异常发生时,JVM会在当前出现异常的方法中,查找异常表,是否有合适的处理者来处理,如果当前方法异常表不为空,并且异常符合处理者的from和to节点,并且type也匹配,则JVM调用位于target的调用者来处理。
  • 如果上一条未找到合理的处理者,则继续查找异常表中的剩余条目。
  • 如果当前方法的异常表无法处理,则向上查找(弹栈处理)刚刚调用该方法的调用处,并重复上面的操作。
  • 如果所有的栈帧被弹出,仍然没有处理,则抛给当前的Thread,Thread则会终止。
  • 如果当前Thread为最后一个非守护线程,且未处理异常,则会导致JVM终止运行。


在看一个实例

public class Main {
	public static void main(String[] args) {
		System.out.println(getInt());
	}
	public static int getInt() {
		int a = 10;
		try {
			System.out.println(a / 0);
			a = 20;
		} catch (ArithmeticException e) {
			a = 30;
			return a;
			/*
			 * return a; 在程序执行到这一步的时候,这里不是return a; 而是return 30; 这个a变量是个副本。
			 * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40 再次回到以前的返回路径,继续走 return a的副本30
			 */
		} finally {
			a = 40;
			// return a; // 如果这样结果就是40了。
		}
		return a;
	}
}

结果运行

30


Java finally 语句到底是在 return 之前还是之后执行?


finally语句在return语句执行之后return返回之前执行的。

finally块中的return语句会覆盖try块中的return返回。

如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变。

try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况。

当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样。


finally 是否一定会执行

两种情况下 finally 不会执行

  • try 模块没有运行。
  • 使用 System.exit(0) 终止JVM。


throws关键字

此关键字主要在方法的声明上使用,表示方法中不处理异常,而交给调用处处理。

RuntimeExcepion与Exception的区别


Integer类: public static int parseInt(String text)throws NumberFormatException
此方法抛出了异常, 但是使用时却不需要进行try。。。catch捕获处理。
原因:
因为NumberFormatException并不是Exception的直接子类,而是RuntimeException的子类,只要是RuntimeException的子类,则表示程序在操作的时候可以不必使用try…catch进行处,如果有异常发生,则由JVM进行处理。当然,也可以通过try catch处理。


强烈推荐一个 进阶 JAVA架构师 的博客

Java架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者