`

jvm研究——异常处理

阅读更多

异常表

每一个try语句块catch的异常都与异常表中的一项相对应,异常表中的每一项都包括:

  1. 起点
  2. 终点,始终把catch异常位置的pc指针偏移量的最大值大1
  3. 处理异常时跳转到的字节码序列中的pc指针偏移量
  4. catch的异常类的常量池索引

 

例如:

public class Test {
	public static void main(String[] args) {

		try {
			Class.forName("java.lang.String");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}
}

 javap –c查看字节码如下:

Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."<init>":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	ldc	#2; //String java.lang.String
   2:	invokestatic	#3; //Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
   5:	pop	
   6:	goto	14
   9:	astore_1
   10:	aload_1
   11:	invokevirtual	#5; //Method java/lang/ClassNotFoundException.printStackTrace:()V
   14:	return
  Exception table:
   from   to  target type
     0     6     9   Class java/lang/ClassNotFoundException
}

 

可见ClassNotFoundException异常可能会在0~6之间抛出,9开始处的代码处理此异常。

 

当产生异常的时候,jvm将会在整个异常表中搜索与之匹配的项,如果当前pc在异常表入口所指的范围内,并且所抛出的异常是此入口所指向的类或者其子类,则跳转到对应的处理代码继续执行。

 

方法可能会抛出哪些已检查异常

Class文件的attribute_info中保存有Exceptions属性,记录着每个方法throws的异常信息。具体的可以查看class类文件格式相关的文章。

 

athrow指令从栈顶弹出Throwable对象引用,抛出异常。

 

finally语句

jvm规范中,finally语句是通过jsr/jsr_wret指令实现的。当执行jsr/jsr_w的时候将finally执行完成后的返回地址压入栈中,进入finally后会马上将此地址保存到一个局部变量中,执行完成后,ret从此局部变量中取出返回地址。???为什么会先把返回地址保存到局部变量中呢???因为,当从finally语句返回的时候需要将返回地址成栈中弹出,当finally语句非正常结束(break,continue,return, 抛异常)的时候就不用再考虑这个问题。

 

以下是jvm规范中Compiling finally的一段:

void tryFinally() {
    try {
        tryItOut();
    } finally {
        wrapItUp();
    }
}
the compiled code is
Method void tryFinally()
   0 	aload_0			// Beginning of try block
   1	invokevirtual #6 		// Method Example.tryItOut()V
   4 	jsr 14			// Call finally block
   7 	return			// End of try block
   8 	astore_1			// Beginning of handler for any throw
   9 	jsr 14			// Call finally block
  12 	aload_1			// Push thrown value
  13 	athrow			// ...and rethrow the value to the invoker
  14 	astore_2			// Beginning of finally block
  15 	aload_0			// Push this
  16 	invokevirtual #5 		// Method Example.wrapItUp()V
  19 	ret 2			// Return from finally block
Exception table:
   	From 	To 	Target 		Type
	0    	4    	8   		any

 

tryItOut排除任何异常后都将会被异常表中的any项捕获,执行完finally后,会执行athrow指令将异常抛出。

 

jdk的某一个版本开始就不会编译出编译出含jsr/jsr_wret的字节码了,因为有指令上的缺陷,导致jvm的检验和分析系统出现漏洞。(从rednaxelafx处得知)

 

再说finally的非正常退出

finally中使用breakcontinuereturn、抛出异常等认为是finally的非正常结束。非正常结束的时候,ret指令不会被执行,很可能会出现意想不到的结果。如:

public class Test {
	public static boolean test(boolean b) {
		while (b) {
			try {
				return true;
			} finally {
				/*
				break;  						始终返回false
				continue; 						javac编译再java执行会出现死循环
												在eclipse中甚至会出现报错:提示找到不main class
				return false; 					始终返回false
				throw new RuntimeException("");	抛出异常
				 */
			}
		}

		return false;
	}

	public static void main(String[] args) {
		System.out.println(test(true));
	}
}
 建议:在写finally语句的时候,尽量避免非正常结束!

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics