JVM 对Java异常的处理原理

最初我们用 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 代码及生成的相应字节码指令。

  1. package  com.unmi;   
  2.   
  3. import  java.io.UnsupportedEncodingException;   
  4.   
  5. public  class  AboutCatch {   
  6.        
  7.     public  static  void  main(String[] args){   
  8.         try  {   
  9.             transfer("JVM 对 Java 异常的处理" ,"gbk" );   
  10.         } catch  (Exception e) {   
  11.             //e.printStackTrace();   
  12.         }   
  13.     }   
  14.        
  15.     //字符集转换的方法   
  16.     public  static  void  transfer(String src, String charset)   
  17.             throws  Exception{   
  18.         String result = "" ;   
  19.         try {   
  20.             //这行代码可能会抛出空指针,不支持的字符集,数组越界的异常   
  21.             result = new  String(src.getBytes(),0 ,10 ,charset);   
  22.         }catch (NullPointerException ne){   
  23.             System.out.println("捕获到异常 ArithemticExcetipn" );   
  24.             throw  ne;   
  25.         }catch (UnsupportedEncodingException uee){   
  26.             System.out.println("捕获到异常 UnsupportedEncodingException" );   
  27.             throw  uee;   
  28.         }catch (Exception ex){ //比如数组越界时在这里可捕获到   
  29.             System.out.println("捕获到异常 Exception" );   
  30.             throw  ex;   
  31.         }   
  32.         System.out.println(result);   
  33.     }   
  34. }   



来看看上面代码中的 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 此刻也就到了生命的尽头。

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值