JVM 对Java异常的处理原理
2009-11-21 10:43
211 查看
最初我们用 Java 写 JSP 的时候,几乎可以不触及异常,因为 Servlet 容器会把 API 抛出的异常包装成
ServletException 丢给容器去处理。再后来应用分层,代码中要处理的异常便多了,一般会转换成自定义的业务异常类,用
try-catch-throw
customerException-finally。再到如今各种框架日臻成熟,代码中显式的异常处理又渐渐少了些,借助于 AOP
横行,异常对业务的影响描述被移入到了配置文件中了,例如,事物处理、权限的控制等。
这颇有些像手机的发展,当通信技术不甚发达的时候,手里抓的是砖头,信号是模拟的。后来慢慢瘦身成两三根手指大小,甚至是就一支笔似的,可如今信息量大了,屏幕要大,再配上 QWERT 键盘,机身自然就肥硕了。
当然与手机的个头变迁略有不同的是,任凭你怎么对待 Java 中异常,切入 AOP 也好,在 JVM 中处理异常的内在机制始终未变。
说到 Java 异常,无外乎就是 try、catch、finally、throw、throws 这么几个关键字,这些个的用法是没必要在这里讲了。我们这里主要关键一下 catch 和 finally 是如何在编译后的 class 字节码中的。
异常的抛出与捕获,Catch 子句的表现,来看看一段 Java 代码及生成的相应字节码指令。
package
com.unmi;
import
java.io.UnsupportedEncodingException;
public
class
AboutCatch {
public
static
void
main(String[] args){
try
{
transfer("JVM 对 Java 异常的处理"
,"gbk"
);
} catch
(Exception e) {
//e.printStackTrace();
}
}
//字符集转换的方法
public
static
void
transfer(String src, String charset)
throws
Exception{
String result = ""
;
try
{
//这行代码可能会抛出空指针,不支持的字符集,数组越界的异常
result = new
String(src.getBytes(),0
,10
,charset);
}catch
(NullPointerException ne){
System.out.println("捕获到异常 ArithemticExcetipn"
);
throw
ne;
}catch
(UnsupportedEncodingException uee){
System.out.println("捕获到异常 UnsupportedEncodingException"
);
throw
uee;
}catch
(Exception ex){ //比如数组越界时在这里可捕获到
System.out.println("捕获到异常 Exception"
);
throw
ex;
}
System.out.println(result);
}
}
来看看上面代码中的 transfer() 方法相应的字节码指令,编译器是 Eclipse 3.3.2 的,它所用的 JDK 是
1.6.0_06,编译兼容级别设置为 6.0。用命令 javap -c com.unmi.AboutCatch 在 Dos 窗口中就能输出:
public static void transfer(java.lang.String, java.lang.String) throws java.lang.Exception;
Code:
0: ldc #30; //String
2: astore_2
3: new #32; //class java/lang/String
6: dup
7: aload_0
8: invokevirtual #34; //Method java/lang/String.getBytes:()[B
11: iconst_0
12: bipush 10
14: aload_1
15: invokespecial #38; //Method java/lang/String."<init>":([BIILjava/lang/String;)V
18: astore_2
19: goto 55 //依据异常表执行完异常处理块后,再回到这里,然后 goto 到 55 号指令继续执行
22: astore_3
23: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
26: ldc #47; //String 捕获到异常 ArithemticExcetipn
28: invokevirtual #49; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: aload_3
32: athrow //抛出 ArthemticException 异常
33: astore_3
34: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #55; //String 捕获到异常 UnsupportedEncodingException
39: invokevirtual #49; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: aload_3
43: athrow //抛出 UnsupportedEncodingException 异常
44: astore_3
45: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
48: ldc #57; //String 捕获到异常 Exception
50: invokevirtual #49; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
53: aload_3
54: athrow //抛出 Exception 异常
55: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
58: aload_2
59: invokevirtual #49; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
62: return
Exception table: //这下面是一个异常表,所以异常不像普通代码那样是靠 goto 语句来跳转的
from to target type
//0-19 号指令中,碰到 NullPointerException时,跳到 22 号指令
3 19 22 Class java/lang/NullPointerException
//0-19 号指令中,碰到 UnsupportedEncodingException 时,跳到 33 号指令
3 19 33 Class java/io/UnsupportedEncodingException
//0-19 号指令中,碰到 NullPointerException时,跳到 44 号指令
3 19 44 Class java/lang/Exception
说明:
对于上面的程序,我们可以用下面代码来调用看看输出
1) transfer("JVM 对 Java 异常的处理","gbk"); //正常
2) transfer(null, "gbk"); //空指针异常
3) transfer("JVM 对","gbk"); //数组越界异常
4) transfer("JVM 对","gbk-1"); //不支持的字符集异常
最后可以把代码中的
catch(Exception ex){ //比如数组越界时在这里可捕获到
System.out.println("捕获到异常 Exception");
throw ex;
}
或是 main() 方法写成
public static void main(String[] args) throws Exception{
transfer("JVM 对 Java 异常的处理","gbk");
}
来试试,异常一直未得到处理对 JVM 的影响
字节码中,红色部分是我加上去的注释,着重描了要关注的地方,其他的出入栈、方法调用的指令可不予以理会,关键是只要知晓有一个异常表的存在,try
的范围就是体现在异常表行记录的起点和终点。JVM 在 try
住的代码区间内如有异常抛出的话,就会在当前栈桢的异常表中,找到匹配类型的异常记录的入口指令号,然后跳到该指令处执行。异常指令块执行完后,再回来继
续执行后面的代码。JVM 按照每个入口在表中出现的顺序进行检索,如果没有发现匹配的项,JVM 将当前栈帧从栈中弹出,再次抛出同样的异常。当
JVM 弹出当前栈帧时,JVM
马上终止当前方法的执行,并且返回到调用本方法的方法中,但是并非继续正常执行该方法,而是在该方法中抛出同样的异常,这就使得 JVM
在该方法中再次执行同样的搜寻异常表的操作。
上面那样的内层方法无法处理异常的层层向外抛,层层压栈,这样就形成一个异常栈。异常栈十分有利于我们透析问题之所在,例如
e.printStackTrace(); 或者带参数的 e.printStackTrace(); 方法可将异常栈信息定向输出到他处,还有
log4j 的 log.error(Throwable) 也有此功效。若是在行径的哪层有能力处理该异常则已,否则直至 JVM,直接造成 JVM
崩溃掉。例如当 main() 方法也把异常抛了出去,JVM 此刻也就到了生命的尽头。
ServletException 丢给容器去处理。再后来应用分层,代码中要处理的异常便多了,一般会转换成自定义的业务异常类,用
try-catch-throw
customerException-finally。再到如今各种框架日臻成熟,代码中显式的异常处理又渐渐少了些,借助于 AOP
横行,异常对业务的影响描述被移入到了配置文件中了,例如,事物处理、权限的控制等。
这颇有些像手机的发展,当通信技术不甚发达的时候,手里抓的是砖头,信号是模拟的。后来慢慢瘦身成两三根手指大小,甚至是就一支笔似的,可如今信息量大了,屏幕要大,再配上 QWERT 键盘,机身自然就肥硕了。
当然与手机的个头变迁略有不同的是,任凭你怎么对待 Java 中异常,切入 AOP 也好,在 JVM 中处理异常的内在机制始终未变。
说到 Java 异常,无外乎就是 try、catch、finally、throw、throws 这么几个关键字,这些个的用法是没必要在这里讲了。我们这里主要关键一下 catch 和 finally 是如何在编译后的 class 字节码中的。
异常的抛出与捕获,Catch 子句的表现,来看看一段 Java 代码及生成的相应字节码指令。
package
com.unmi;
import
java.io.UnsupportedEncodingException;
public
class
AboutCatch {
public
static
void
main(String[] args){
try
{
transfer("JVM 对 Java 异常的处理"
,"gbk"
);
} catch
(Exception e) {
//e.printStackTrace();
}
}
//字符集转换的方法
public
static
void
transfer(String src, String charset)
throws
Exception{
String result = ""
;
try
{
//这行代码可能会抛出空指针,不支持的字符集,数组越界的异常
result = new
String(src.getBytes(),0
,10
,charset);
}catch
(NullPointerException ne){
System.out.println("捕获到异常 ArithemticExcetipn"
);
throw
ne;
}catch
(UnsupportedEncodingException uee){
System.out.println("捕获到异常 UnsupportedEncodingException"
);
throw
uee;
}catch
(Exception ex){ //比如数组越界时在这里可捕获到
System.out.println("捕获到异常 Exception"
);
throw
ex;
}
System.out.println(result);
}
}
package com.unmi; import java.io.UnsupportedEncodingException; public class AboutCatch { public static void main(String[] args){ try { transfer("JVM 对 Java 异常的处理","gbk"); } catch (Exception e) { //e.printStackTrace(); } } //字符集转换的方法 public static void transfer(String src, String charset) throws Exception{ String result = ""; try{ //这行代码可能会抛出空指针,不支持的字符集,数组越界的异常 result = new String(src.getBytes(),0,10,charset); }catch(NullPointerException ne){ System.out.println("捕获到异常 ArithemticExcetipn"); throw ne; }catch(UnsupportedEncodingException uee){ System.out.println("捕获到异常 UnsupportedEncodingException"); throw uee; }catch(Exception ex){ //比如数组越界时在这里可捕获到 System.out.println("捕获到异常 Exception"); throw ex; } System.out.println(result); } }
来看看上面代码中的 transfer() 方法相应的字节码指令,编译器是 Eclipse 3.3.2 的,它所用的 JDK 是
1.6.0_06,编译兼容级别设置为 6.0。用命令 javap -c com.unmi.AboutCatch 在 Dos 窗口中就能输出:
public static void transfer(java.lang.String, java.lang.String) throws java.lang.Exception;
Code:
0: ldc #30; //String
2: astore_2
3: new #32; //class java/lang/String
6: dup
7: aload_0
8: invokevirtual #34; //Method java/lang/String.getBytes:()[B
11: iconst_0
12: bipush 10
14: aload_1
15: invokespecial #38; //Method java/lang/String."<init>":([BIILjava/lang/String;)V
18: astore_2
19: goto 55 //依据异常表执行完异常处理块后,再回到这里,然后 goto 到 55 号指令继续执行
22: astore_3
23: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
26: ldc #47; //String 捕获到异常 ArithemticExcetipn
28: invokevirtual #49; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: aload_3
32: athrow //抛出 ArthemticException 异常
33: astore_3
34: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
37: ldc #55; //String 捕获到异常 UnsupportedEncodingException
39: invokevirtual #49; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: aload_3
43: athrow //抛出 UnsupportedEncodingException 异常
44: astore_3
45: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
48: ldc #57; //String 捕获到异常 Exception
50: invokevirtual #49; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
53: aload_3
54: athrow //抛出 Exception 异常
55: getstatic #41; //Field java/lang/System.out:Ljava/io/PrintStream;
58: aload_2
59: invokevirtual #49; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
62: return
Exception table: //这下面是一个异常表,所以异常不像普通代码那样是靠 goto 语句来跳转的
from to target type
//0-19 号指令中,碰到 NullPointerException时,跳到 22 号指令
3 19 22 Class java/lang/NullPointerException
//0-19 号指令中,碰到 UnsupportedEncodingException 时,跳到 33 号指令
3 19 33 Class java/io/UnsupportedEncodingException
//0-19 号指令中,碰到 NullPointerException时,跳到 44 号指令
3 19 44 Class java/lang/Exception
说明:
对于上面的程序,我们可以用下面代码来调用看看输出
1) transfer("JVM 对 Java 异常的处理","gbk"); //正常
2) transfer(null, "gbk"); //空指针异常
3) transfer("JVM 对","gbk"); //数组越界异常
4) transfer("JVM 对","gbk-1"); //不支持的字符集异常
最后可以把代码中的
catch(Exception ex){ //比如数组越界时在这里可捕获到
System.out.println("捕获到异常 Exception");
throw ex;
}
或是 main() 方法写成
public static void main(String[] args) throws Exception{
transfer("JVM 对 Java 异常的处理","gbk");
}
来试试,异常一直未得到处理对 JVM 的影响
字节码中,红色部分是我加上去的注释,着重描了要关注的地方,其他的出入栈、方法调用的指令可不予以理会,关键是只要知晓有一个异常表的存在,try
的范围就是体现在异常表行记录的起点和终点。JVM 在 try
住的代码区间内如有异常抛出的话,就会在当前栈桢的异常表中,找到匹配类型的异常记录的入口指令号,然后跳到该指令处执行。异常指令块执行完后,再回来继
续执行后面的代码。JVM 按照每个入口在表中出现的顺序进行检索,如果没有发现匹配的项,JVM 将当前栈帧从栈中弹出,再次抛出同样的异常。当
JVM 弹出当前栈帧时,JVM
马上终止当前方法的执行,并且返回到调用本方法的方法中,但是并非继续正常执行该方法,而是在该方法中抛出同样的异常,这就使得 JVM
在该方法中再次执行同样的搜寻异常表的操作。
上面那样的内层方法无法处理异常的层层向外抛,层层压栈,这样就形成一个异常栈。异常栈十分有利于我们透析问题之所在,例如
e.printStackTrace(); 或者带参数的 e.printStackTrace(); 方法可将异常栈信息定向输出到他处,还有
log4j 的 log.error(Throwable) 也有此功效。若是在行径的哪层有能力处理该异常则已,否则直至 JVM,直接造成 JVM
崩溃掉。例如当 main() 方法也把异常抛了出去,JVM 此刻也就到了生命的尽头。
相关文章推荐
- JVM对Java异常的处理原理
- Java中的异常处理机制的简单原理和应用
- (java)异常处理原理和原则
- 深入理解java异常处理机制的原理和开发应用
- Java中的异常处理机制的简单原理和应用
- 牛客网Java刷题知识点之什么是异常、异常处理的原理是什么、为什么要使用异常、异常体系、运行时异常、普通异常、自定义异常、异常链
- Java中的异常处理机制的简单原理?
- Java中的异常处理机制的简单原理和应用
- Java中的异常处理机制的简单原理和应用
- Java中的异常处理机制的简单原理和应用
- java中的异常处理机制的简单原理和应用
- 【Java面试题】21 Java中的异常处理机制的简单原理和应用。
- Java中异常处理机制的简单原理和应用,并说明Error与Exception有什么区别?
- Java中的异常处理机制的简单原理和应用
- Java中的异常处理机制的简单原理和应用
- Java 中的异常处理机制的简单原理和应用?
- 从字节码角度理解JVM异常处理机制的原理
- java异常处理原理及应用
- Java中的异常处理机制的简单原理和应用。
- 请描述Java中异常处理机制的简单原理和应用,并说明Error与Exception有什么区别?