Java虚拟机-异常的处理

2019-08-03  本文已影响0人  贪睡的企鹅

1 异常处理

异常处理的两大组成要素是抛出异常捕获异常。这两大要素共同实现程序控制流的非正常转移。

2 抛出异常

抛出异常可分为显式隐式两种。

3 捕获异常

捕获异常则涉及了如下三种代码块。

3.1 案例
public class TryCatchFinally {
    private String tryBlock = "tryBlock";
    private String catchBlock = "catchBlock";
    private String finallyBlock = "finallyBlock";

    public String test() {
        try {
            System.out.println(tryBlock);
            //int a = 5/0;  //异常抛出点
            return tryBlock;
        } catch (Exception e) {
            //int a = 5/0;  //异常抛出点
            System.out.println(catchBlock);
            return catchBlock;
        } finally {
            //int a = 5/0;  //异常抛出点
            System.out.println(finallyBlock);
            //return finallyBlock;
        }
    }

    public static void main(String[] args) {
        System.out.println(new TryCatchFinally().test());
    }
}
3.1 程序正常运行
tryBlock
finallyBlock
tryBlock    
3.2 程序异常运行
打开try中注释//int a = 5/0;

tryBlock
catchBlock
finallyBlock
catchBlock    
3.2 finally中返回

无论正常运行还是异常运行都返回"finallyBlock"

打开注释 //return finallyBlock;

//正常运行
tryBlock
finallyBlock
finallyBlock

//异常运行
tryBlock
catchBlock
finallyBlock
finallyBlock 
3.2 finally中抛出异常

中断finally中语句,对外抛出异常

打开finally中注释//int a = 5/0;

//正常运行
tryBlock

//异常运行
tryBlock
catchBlock
3.2 catch中抛出异常

中止发生异常后中语句,执行finally块中语句,并对外抛出异常

打开catch中注释//int a = 5/0;
打开try中注释//int a = 5/0;

tryBlock
finallyBlock

4 throws声明异常

使用throws声明在方法上声明可能出现的异常。throws是另一种处理异常的方式,它不同于try...catch...finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。

throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。

public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{ 
     //foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
}

4 异常类型

在 Java 语言规范中,所有异常都是 Throwable 类或者其子类的实例。Throwable 有两大直接子类。第一个是 Error,涵盖程序不应捕获的异常。第二子类则是 Exception,涵盖程序可能需要捕获并且处理的异常。

image

Exception 有一个特殊的子类 RuntimeException,用来表示“程序虽然无法继续执行,但是还能抢救一下”的情况。前边提到的数组索引越界便是其中的一种。

RuntimeException 和 Error 属于 Java 里的非检查异常(unchecked exception)。其他异常则属于检查异常(checked exception)。在 Java 语法中,所有的检查异常都需要程序显式地捕获,或者在方法声明中用 throws 关键字标注。通常情况下,程序中自定义的异常应为检查异常,以便最大化利用 Java 编译器的编译时检查。

5 异常的链化

查看Throwable类源码,可以发现里面有一个Throwable字段cause,就是以一个异常对象为参数构造新的异常对象。

public class Throwable implements Serializable {
    private Throwable cause = this;
   
    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
     public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }
    
    //........
}

案例

try {

    }catch(InputMismatchException immExp){
        throw new Exception("计算失败",immExp);  /////////////////////////////链化:以一个异常对象为参数构造新的异常对象。
    }

java.lang.Exception: 计算失败
    at practise.ExceptionTest.add(ExceptionTest.java:53)
    at practise.ExceptionTest.main(ExceptionTest.java:18)
Caused by: java.util.InputMismatchException
    at java.util.Scanner.throwFor(Scanner.java:864)
    at java.util.Scanner.next(Scanner.java:1485)
    at java.util.Scanner.nextInt(Scanner.java:2117)
    at java.util.Scanner.nextInt(Scanner.java:2076)
    at practise.ExceptionTest.getInputNumbers(ExceptionTest.java:30)
    at practise.ExceptionTest.add(ExceptionTest.java:48)
    ... 1 more    

6 异常的信息

异常实例的构造十分昂贵。这是由于在构造异常实例时,Java 虚拟机便需要生成该异常的栈轨迹(stack trace)。该操作会逐一访问当前线程的 Java 栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常。

image

7 java虚拟机中异常处理

程序源文件

public class TryCatchFinally {
    private String tryBlock = "tryBlock";
    private String catchBlock = "catchBlock";
    private String finallyBlock = "finallyBlock";

    public String test() {
        try {
            System.out.println(tryBlock);
            //throw new RunTimeException();
            return tryBlock;
        } catch (Exception e) {
            System.out.println(catchBlock);
            return catchBlock;
        } finally {
            System.out.println(finallyBlock);
            //return finallyBlock;
        }
    }

    public static void main(String[] args) {
        System.out.println(new TryCatchFinally().test());
    }
}

程序编译后字节码指令

  public java.lang.String test();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #3                  // Field tryBlock:Ljava/lang/String;
         7: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: aload_0
        11: getfield      #3                  // Field tryBlock:Ljava/lang/String;
        14: astore_1
        15: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: aload_0
        19: getfield      #7                  // Field finallyBlock:Ljava/lang/String;
        22: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: aload_1
        26: areturn
        27: astore_1
        28: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        31: aload_0
        32: getfield      #5                  // Field catchBlock:Ljava/lang/String;
        35: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        38: aload_0
        39: getfield      #5                  // Field catchBlock:Ljava/lang/String;
        42: astore_2
        43: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        46: aload_0
        47: getfield      #7                  // Field finallyBlock:Ljava/lang/String;
        50: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        53: aload_2
        54: areturn
        55: astore_3
        56: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        59: aload_0
        60: getfield      #7                  // Field finallyBlock:Ljava/lang/String;
        63: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        66: aload_3
        67: athrow
      Exception table:
         from    to  target type
             0    15    27   Class java/lang/Exception
             0    15    55   any
            27    43    55   any
7.1 Jvm如何实现异常捕获
 Exception table:
         from    to  target type
             0    15    30   Class java/lang/Exception
             0    15    61   any
            30    46    61   any
from    to  target  type
   0    15    30    Class java/lang/Exception

//对应Java源文件代码
try {
     System.out.println(tryBlock);
     return tryBlock;
} catch (Exception e) {            
from    to  target type
   0    15    61   any
  30    46    61   any
7.2 程序的正常运行
0: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield      #3                  // Field tryBlock:Ljava/lang/String;
7: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

//对应Java源文件代码
try {
     System.out.println(tryBlock);
     ...
}     

10: aload_0
11: getfield      #3                  // Field tryBlock:Ljava/lang/String;
14: astore_1

//对应Java源文件代码
try {
     ...
     return tryBlock;
}     
15: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
18: aload_0
19: getfield      #7                  // Field finallyBlock:Ljava/lang/String;
22: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

//对应Java源文件代码
finally {
    System.out.println(finallyBlock);
}
25: aload_1
26: areturn

//对应Java源文件代码
try {
     ...
     return tryBlock;
} 
7.3 捕获异常处理

按照异常表到发生Exception异常程序会跳到27行指令运行

from    to  target   type
   0    15      27   Class java/lang/Exception
27: astore_1
28: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
31: aload_0
32: getfield      #5                  // Field catchBlock:Ljava/lang/String;
35: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

//对应Java源文件代码
} catch (Exception e) {
    System.out.println(catchBlock);
    ...
}
38: aload_0
39: getfield      #5                  // Field catchBlock:Ljava/lang/String;

//对应Java源文件代码
} catch (Exception e) {
    ...
    return catchBlock;
}
43: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_0
47: getfield      #7                  // Field finallyBlock:Ljava/lang/String;
50: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

//对应Java源文件代码
finally {
    System.out.println(finallyBlock);
}
53: aload_2
54: areturn

//对应Java源文件代码
} catch (Exception e) {
    ...
    return catchBlock;
}
7.4 未捕获异常处理

如果try语句发生了catch中未定义的异常,或者catch代码块发生了异常则叫由JVM捕获执行,跳到61行指令运行

from    to  target type
   0    15    61   any
  30    46    61   any
55: astore_3
56: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
59: aload_0
60: getfield      #7                  // Field finallyBlock:Ljava/lang/String;
63: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: aload_3
67: athrow
7.5 小结
image

8 Java 7 的 Supressed 异常以及语法糖

  FileInputStream in0 = null;
  FileInputStream in1 = null;
  FileInputStream in2 = null;
  ...
  try {
    in0 = new FileInputStream(new File("in0.txt"));
    ...
    try {
      in1 = new FileInputStream(new File("in1.txt"));
      ...
      try {
        in2 = new FileInputStream(new File("in2.txt"));
        ...
      } finally {
        if (in2 != null) in2.close();
      }
    } finally {
      if (in1 != null) in1.close();
    }
  } finally {
    if (in0 != null) in0.close();
  }

Java 7 的 try-with-resources 语法糖,极大地简化了上述代码。程序可以在 try 关键字后声明并实例化实现了 AutoCloseable 接口的类,编译器将自动添加对应的 close() 操作。在声明多个 AutoCloseable 实例的情况下,编译生成的字节码类似于上面手工编写代码的编译结果。与手工代码相比,try-with-resources 还会使用 Supressed 异常的功能,来避免原异常“被消失”。

public class Foo implements AutoCloseable {
  private final String name;
  public Foo(String name) { this.name = name; }

  @Override
  public void close() {
    throw new RuntimeException(name);
  }

  public static void main(String[] args) {
    try (Foo foo0 = new Foo("Foo0"); // try-with-resources
         Foo foo1 = new Foo("Foo1");
         Foo foo2 = new Foo("Foo2")) {
      throw new RuntimeException("Initial");
    }
  }
}

// 运行结果:
Exception in thread "main" java.lang.RuntimeException: Initial
        at Foo.main(Foo.java:18)
        Suppressed: java.lang.RuntimeException: Foo2
                at Foo.close(Foo.java:13)
                at Foo.main(Foo.java:19)
        Suppressed: java.lang.RuntimeException: Foo1
                at Foo.close(Foo.java:13)
                at Foo.main(Foo.java:19)
        Suppressed: java.lang.RuntimeException: Foo0
                at Foo.close(Foo.java:13)
                at Foo.main(Foo.java:19)


除了 try-with-resources 语法糖之外,Java 7 还支持在同一 catch 代码块中捕获多种异常。实际实现非常简单,生成多个异常表条目即可。

// 在同一 catch 代码块中捕获多种异常
try {
  ...
} catch (SomeException | OtherException e) {
  ...
}

上一篇 下一篇

猜你喜欢

热点阅读