理解java exception

一直对 Java 异常的实现机制搞得不是很懂,也一直没找到比较权威的解释,国内各大论坛一通搜索也没找到合理的解释以消除我的疑问。

貌似只有我抱着这个疑问,可能是国内大牛比较多,这个问题又如此简单,大家已经心照不宣了。

最后终于还是看了一通官方文档才真正搞明白,以下是一些摘要。

3.10 Exceptions

In the Java programming language, throwing an exception results in an immediate nonlocal transfer of control from the point where the exception was thrown. This transfer of control may abruptly complete, one by one, multiple statements, constructor invocations, static and field initializer evaluations, and method invocations. The process continues until a catch clause is found that handles the thrown value. If no such clause can be found, the current thread exits.

In cases where a finally clause is used, the finally clause is executed during the propagation of an exception thrown from the associated try block and any associated catch block, even if no catch clause that handles the thrown exception may be found.

As implemented by the Java virtual machine, each catch or finally clause of a method is represented by an exception handler. An exception handler specifies the range of offsets into the Java virtual machine code implementing the method for which the exception handler is active, describes the type of exception that the exception handler is able to handle, and specifies the location of the code that is to handle that exception. An exception matches an exception handler if the offset of the instruction that caused the exception is in the range of offsets of the exception handler and the exception type is the same class as or a subclass of the class of exception that the exception handler handles. When an exception is thrown, the Java virtual machine searches for a matching exception handler in the current method. If a matching exception handler is found, the system branches to the exception handling code specified by the matched handler.

If no such exception handler is found in the current method, the current method invocation completes abruptly. On abrupt completion, the operand stack and local variables of the current method invocation are discarded, and its frame is popped, reinstating the frame of the invoking method. The exception is then rethrown in the context of the invoker’s frame and so on, continuing up the method invocation chain. If no suitable exception handler is found before the top of the method invocation chain is reached, the execution of the thread in which the exception was thrown is terminated.

The order in which the exception handlers of a method are searched for a match is important. Within a class file the exception handlers for each method are stored in a table. At run time, when an exception is thrown, the Java virtual machine searches the exception handlers of the current method in the order that they appear in the corresponding exception handler table in the class file, starting from the beginning of that table. Because try statements are structured, a compiler for the Java programming language can always order the entries of the exception handler table such that, for any thrown exception and any program counter value in that method, the first exception handler that matches the thrown exception corresponds to the innermost matching catch or finally clause.

Note that the Java virtual machine does not enforce nesting of or any ordering of the exception table entries of a method. The exception handling semantics of the Java programming language are implemented only through cooperation with the compiler. When class files are generated by some other means, the defined search procedure ensures that all Java virtual machines will behave consistently.

athrow

The objectref must be of type reference and must refer to an object that is an instance of class Throwable or of a subclass of Throwable. It is popped from the operand stack. The objectref is then thrown by searching the current method for the first exception handler that matches the class of objectref, as given by the algorithm in §3.10.
If an exception handler that matches objectref is found, it contains the location of the code intended to handle this exception. The pc register is reset to that location, the operand stack of the current frame is cleared, objectref is pushed back onto the operand stack, and execution continues.
If no matching exception handler is found in the current frame, that frame is popped. If the current frame represents an invocation of a synchronized method, the monitor acquired or reentered on invocation of the method is released or exited (respectively) as if by execution of a monitorexit instruction. Finally, the frame of its invoker is reinstated, if such a frame exists, and the objectref is rethrown. If no such frame exists, the current thread exits.

对于这个解释我做了一个简单的示例方便理解

public static void main(String[] args)
{
   try
   {
      throw new IOException();
   }
   catch (IOException ioe)
   {
      System.out.println("io exception");
   }
   catch (Exception e)
   {
      System.out.println("other exception");
   }
}
 
 
/* 以下是字节码的反汇编
public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/io/IOException
       3: dup
       4: invokespecial #3                  // Method java/io/IOException."<init>":()V
       7: athrow
       8: astore_1
       9: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: ldc           #5                  // String io exception
      14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      17: goto          29
      20: astore_1
      21: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      24: ldc           #8                  // String other exception
      26: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      29: return
    Exception table:
       from    to  target type
           0     8     8   Class java/io/IOException
           0     8    20   Class java/lang/Exception
*/

大致描述以下我的理解:

首先抛出异常时,会调用 athrow 指令,这个指令直接操作栈顶进行弹出操作,此时栈顶的变量一定是一个指向 Throwable(或其子类) 对象的引用,编译器会保证这一点;异常抛出后,JVM会查找当前方法中与之匹配的exception handler,即catch语句和 finally 语句(编译器会为每个 method 保存了一个exception table,对应每一个 catch 和 finally),如果存在匹配的 handler,则调用它;如果不存在,那么当前方法的栈帧会立即弹开,也就是回收整个栈帧,并且将异常重新抛给外层方法,直到找到一个匹配的 exception handler 为止,如果整个线程都没有捕获该异常,则会导致线程终止。

上述整个过程都是JVM对 athrow 指令的执行过程,换句话说java中异常相关的关键字只有throw存在与之对应的JVM指令(Runtime),而 try catch finally 几个关键字则需要编译器和JVM配合实现(Compile time),即通过 exception table 和 goto指令等完成,因为try catch finally 的意义本身就是异常处理过程的声明,并不具有执行的动作。这样也解释了为什么基于JVM的语言可以存在 unchecked exception,因为 checked exception 仅是编译期的检查行为