• 作者:老汪软件技巧
  • 发表时间:2024-11-30 00:03
  • 浏览量:

引言

Java 虚拟机里面的异常使用 Throwable 或其子类的实例来表示,抛异常的本质实际上是程序控制权的一种即时的、非局部(Nonlocal)的转换——从异常抛出的地方转换至处理异常的地方。绝大多数的异常的产生都是由于当前线程执行的某个操作所导致的,这种可以称为是同步的异常。与之相对的,异步异常是指在程序的其他任意地方进行的动作而导致的异常。 Java 虚拟机中异常的出现总是由下面三种原因之一导致的:

1. 虚拟机同步检测到程序发生了非正常的执行情况,这时异常将会紧接着在发生非正常执行情况的字节码指令之后抛出。

2. athrow 字节码指令被执行。

3. 由于以下原因,导致了异步异常的出现:

理解异常

Java异常的底层实现涉及到编译器和虚拟机(JVM)两个层面。包括编译器如何处理异常代码以及虚拟机如何在运行时处理异常。

编译器层面示例

try {
    // 可能引发异常的代码
} catch (SomeException e) {
    // 处理 SomeException 的代码
} finally {
    // 无论是否发生异常都会执行的代码
}

编译器处理

编译器在将源代码编译成字节码时,会对异常相关的代码进行处理。

生成异常表(Exception Table): 编译器会生成一个异常表,其中包含了 try 块的起始和结束位置,以及每个 catch 块和 finally 块的起始位置。这个表是在字节码中的一部分,用于在运行时确定异常处理逻辑。异常处理代码的插入: 编译器会在可能引发异常的代码周围插入异常处理代码,以确保异常发生时能够跳转到正确的 catch 块或 finally 块。虚拟机层面JVM实现

JVM在运行时负责执行编译生成的字节码。

异常对象的创建: 当在 try 块中的代码引发异常时,JVM会创建一个异常对象,其中包含有关异常的信息,如类型、消息和堆栈跟踪。异常抛出: JVM使用 athrow 指令将异常对象抛出。这通常由 throw 关键字触发。异常处理表的使用: 当异常被抛出时,JVM会检查当前方法的异常处理表。它会逐个检查 try 块,看是否匹配抛出的异常。如果找到匹配的 catch 块,JVM会跳转到该块的代码执行异常处理逻辑。finally 块的执行: 无论是否发生异常,JVM都会执行 finally 块中的代码。这是通过在 try 块的最后插入 finally 指令实现的。源码示例

以下是 try-catch-finally 示例

package com.example.demo.exception;
public class TryCatchFinallyExample {
    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Caught ArithmeticException: " + e.getMessage());
        } finally {
            System.out.println("Finally block executed");
        }
    }
    public static int divide(int a, int b) {
        return a / b;
    }
}

对应的字节码(使用 javap -c 命令查看):

先执行编译命令 javac TryCatchFinallyExample.java在执行 javap -c TryCatchFinallyExample

Compiled from "TryCatchFinallyExample.java"
public class com.example.demo.exception.TryCatchFinallyExample {
  public com.example.demo.exception.TryCatchFinallyExample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return
  public static void main(java.lang.String[]);
    Code:
       0: bipush        10
       2: iconst_0
       3: invokestatic  #2                  // Method divide:(II)I
       6: istore_1
       7: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: iload_1
      11: invokedynamic #4,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
      16: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      19: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #6                  // String Finally block executed
      24: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          68
      30: astore_1
      31: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: aload_1
      35: invokevirtual #8                  // Method java/lang/ArithmeticException.getMessage:()Ljava/lang/String;
      38: invokedynamic #9,  0              // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
      43: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      46: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      49: ldc           #6                  // String Finally block executed
      51: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      54: goto          68
      57: astore_2
      58: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      61: ldc           #6                  // String Finally block executed
      63: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

抛异常的作用__抛出异常怎么写

66: aload_2 67: athrow 68: return Exception table: from to target type 0 19 30 Class java/lang/ArithmeticException 0 19 57 any 30 46 57 any public static int divide(int, int); Code: 0: iload_0 1: iload_1 2: idiv 3: ireturn }

Exception table(异常表)

Exception table 是Java字节码中的一个部分,用于指定方法中的异常处理信息。它描述了在方法执行期间,哪些字节码范围可能抛出异常,以及如何处理这些异常。

我们具体解释 Exception table 部分的含义:

Exception table:
   from    to  target type
       0    19    30   Class java/lang/ArithmeticException
       0    19    57   any
      30    46    57   any

每一行代表一个异常处理条目,它包含以下信息:

第一行: 如果0到19之间,发生了ArithmeticException类型的异常,调用30的位置处理异常。

第二行: 如果0到19之间,发生了任何类型的异常,调用57的位置处理异常。

第三行: 如果30到46之间(即catch部分),发生了任何类型的异常,调用57的位置处理异常。

通过这个异常表的信息,它告诉Java虚拟机在执行方法时,如果在指定的范围内发生了异常,应该如何处理。每个异常处理条目都包含了异常的类型和处理的范围。如果异常发生在范围内,程序将按照异常处理表中指定的方式进行处理,跳转到相应的目标位置。

再次分析上面的指令

 public static void main(java.lang.String[]);
    Code:
    
       // try 获取 finally 的代码,如果没有异常发生,则执行输出finally的操作,跳到goto的68位置,执行返回操作。  
       0: bipush        10
       2: iconst_0
       3: invokestatic  #2                  // Method divide:(II)I
       6: istore_1
       7: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: iload_1
      11: invokedynamic #4,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
      16: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      19: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #6                  // String Finally block executed
      24: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          68
      
      // catch 获取 finally代码,如果没有异常发生,则执行输出finally的操作,跳到goto的68位置,执行返回操作。
      30: astore_1
      31: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: aload_1
      35: invokevirtual #8                  // Method java/lang/ArithmeticException.getMessage:()Ljava/lang/String;
      38: invokedynamic #9,  0              // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
      43: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      46: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      49: ldc           #6                  // String Finally block executed
      51: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      54: goto          68
      
      // finally 的代码如果被调用,既有可能是try的异常,也有可能是catch的异常。
      57: astore_2
      58: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      61: ldc           #6                  // String Finally block executed
      63: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      66: aload_2
      
      // 如果异常没有被catch捕获,到了这里,执行完finally的语句后,也要把这个异常抛出去,传递给调用处。
      67: athrow
      68: return

关于指令的解释

关于指令的操作,大家可以阅读《Java虚拟机规范》- 第 6 章 Java 虚拟机指令集。

总结

当程序执行过程中发生异常时,Java虚拟机(JVM)会按照以下流程处理异常:

请在此添加图片描述

执行 try : 程序执行到 try 块中的字节码指令。检测异常发生: 当在 try 块中发生异常时,Java虚拟机会检测到异常的发生。异常表匹配: 异常表是在编译时生成的,它包含了每个 try-catch 块的起始位置、结束位置、异常处理器的位置以及期望捕获的异常类型。异常表将被检查以查找与发生的异常类型匹配的处理器。执行字节码指令: 在 try 块中的字节码指令将继续执行,直到异常发生。抛出异常: 当异常发生时,Java虚拟机会创建一个异常对象,并将其抛出。查找匹配的异常处理器: 异常表中的每一项都将被检查,如果发生的异常类型匹配,就会选择相应的异常处理器。遇到异常处理指令: 当匹配到异常处理器时,控制流将跳转到异常处理器的起始位置。这可能涉及到 goto 指令或其他控制流程的改变。异常表中的处理器执行: 执行异常处理器(catch 块或 finally 块)中的字节码指令。在 catch 块中,会进行对异常对象的处理,而 finally 块则无论是否发生异常都会执行。执行 catch 或 finally: 在异常处理器中执行相应的字节码指令,处理异常或执行清理代码。控制流继续执行: 一旦异常处理完成,程序的控制流将继续执行异常处理代码块之后的代码。


上一条查看详情 +Docker容器的运行整体流程详细解释
下一条 查看详情 +没有了