Java#Excepción

Introducción

Throwable es la clase raíz de todas las excepciones en el lenguaje Java. Throwable deriva dos subclases directas, Error y Excepción. El error representa un problema grave que la aplicación por sí sola no puede superar ni recuperarse. Cuando se activa un error, el hilo o incluso la máquina virtual finalizarán. Las excepciones representan problemas que el programa puede superar y recuperar. Las excepciones se pueden dividir en excepciones en tiempo de compilación y excepciones en tiempo de ejecución según el tiempo de procesamiento.

Las excepciones en tiempo de compilación son excepciones que se pueden reparar. Durante la compilación del código, el programa Java debe manejar explícitamente las excepciones en tiempo de compilación; de lo contrario, no se puede compilar. Las excepciones en tiempo de ejecución suelen ser problemas causados ​​por una mala consideración por parte de los desarrolladores de software.usuario de softwareEste problema no se puede superar ni restaurar, pero el sistema de software puede continuar ejecutándose bajo este problema y, en casos graves, el sistema de software dejará de funcionar.

Por favor agregue la descripción de la imagen.

Manejo de excepciones

Plan de manejo de excepciones

Hay dos opciones para el manejo de excepciones en Java: capturar y manejar excepciones y lanzar excepciones.

Hay dos opciones para el manejo de excepciones en tiempo de compilación: si el método actual sabe cómo manejar la excepción, la detectará. Si el método actual no sabe cómo manejarlo, declararlo para lanzar la excepción al definir el método.

Las excepciones de tiempo de ejecución solo se encuentran cuando el código se está ejecutando y no es necesario detectarlas durante la compilación. Por ejemplo, si el divisor es 0, el subíndice de la matriz está fuera de los límites, etc., ocurren con frecuencia y son problemáticos de manejar. La declaración o captura de pantalla tendrá un gran impacto en la legibilidad y la eficiencia operativa del programa. Por lo tanto, la máquina virtual lo detecta y lo lanza automáticamente. Por supuesto, el procesamiento de captura también se puede mostrar activamente.

Palabras clave relacionadas con excepciones

El mecanismo de excepción de Java se basa principalmente en las cinco palabras clave try, catch, finalmente, throw y throws. Generalmente, try, catch y finalmente se usan en combinación para detectar excepciones. throws y throw se usan solos para generar excepciones.

      // try catch finally
       try {
    
    
             // 可能触发异常的代码
        } catch (XXXException e) {
    
     // XXXException  :代表异常类型 
            // 这里进行处理异常
        } finally {
    
    
           //这里进行资源释放
       }

El bloque de código expandido entre llaves inmediatamente después del intento se llama bloque de prueba, y el código que puede causar una excepción se coloca dentro del bloque de prueba. Defina un tipo de excepción y un bloque de código después de la captura. Cuando una sección de código en el bloque try desencadena una excepción y coincide con el tipo de excepción definido por catch, se sigue la lógica de procesamiento del bloque catch.

El bloque de código de prueba puede ir seguido de múltiples bloques de código de captura para detectar diferentes tipos de excepciones. La máquina virtual Java hace coincidir los controladores de excepciones de arriba a abajo. Por lo tanto, el tipo de excepción capturado por el bloque de código de captura anterior no puede cubrir el siguiente; de ​​lo contrario, el compilador informará un error.

Después del bloque finalmente y el bloque catch, el bloque finalmente se utiliza para reciclar los recursos físicos abiertos en el bloque try. El mecanismo de excepción garantizará que el bloque finalmente se ejecute siempre.


    //throws 抛出异常,方法签名处抛出。
    private static void test() throws XXXException {
    
    }
    
    //throw 作为语句使用,代码中直接抛出一个异常。 throw new XXXException();
    private static void getCode(String type) {
    
    
       if (type == null) throw new IllegalArgumentException("参数不能为空");
    } 

La palabra clave throws se utiliza principalmente en firmas de métodos para declarar excepciones que el método puede generar. throw se usa para lanzar una excepción real. throw se puede usar como una declaración separada para lanzar un objeto de excepción específico.

Castaño de manejo de excepciones

(1) Excepción de tiempo de compilación

    public static void main(String[] args) {
    
    
        File file = new File("F://a.txt");
        if (!file.exists()) {
    
    
            file.createNewFile(); // 这段代码直接运行,这里编译不通过,报编译时异常。
        }
    }

Las excepciones en tiempo de compilación informarán errores durante la compilación del código (tenga en cuenta que los errores informados durante la compilación no son necesariamente excepciones en tiempo de compilación). Dichas excepciones deben detectarse o generarse manualmente durante la codificación ~

/**
 * Create by SunnyDay on 2022/04/21 18:26
 */
public class ExceptionDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        tryCatch();
        throwsException(); // 这里选择继续抛出给main
    }

    /**
     * 捕获异常栗子
     */
    private static void tryCatch() {
    
    
        try {
    
    
            File file = new File("F://a.txt");
            if (!file.exists()) {
    
    
                file.createNewFile();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            System.out.println("finally 块");
        }

    }

    /**
     * 方法签名处抛出异常栗子。
     * 注意若是此方法被其他方法A调用,那么A需要捕获或者抛出处理。
     */
    private static void throwsException() throws IOException {
    
    
        File file = new File("F://a.txt");
        if (!file.exists()) {
    
    
            file.createNewFile();
        }
    }
}

(2) Excepción de tiempo de ejecución

    private static void runtimeException(String name) {
    
    
        name.length(); //name 为空时java.lang.NullPointerException.直接crash。
    }

Las excepciones en tiempo de ejecución generalmente son causadas por la mala consideración del código por parte de los desarrolladores. Generalmente, no es necesario capturarlas o lanzarlas activamente, pero si es necesario, se pueden capturar y manejar activamente de la siguiente manera.

    /**
     * 不过一般不会建议采取捕获处理的方式,完全可通过name的判空处理。
     */
    private static void runtimeException(String name) {
    
    
        // 捕获,出现异常也不会导致crash,不影响try catch 块之外的逻辑。
        try {
    
    
            name.length(); //java.lang.NullPointerException
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

(3) Acceder a información anormal

Si necesita acceder a información relevante sobre el objeto de excepción en el bloque catch, puede obtenerla accediendo a los parámetros de excepción después del bloque catch. Cuando la JVM decide llamar a un bloque catch para manejar el objeto de excepción, asignará el objeto de excepción al parámetro de excepción después del bloque catch. En este momento, podemos usar este parámetro para obtener información relacionada con la excepción. Los métodos comúnmente utilizados son los siguientes:

  • getMessage(): devuelve una cadena de descripción detallada de la excepción.
  • printStackTrace(): envía la información de la pila de seguimiento de excepciones a la salida de error estándar.
  • printStackTrace (PrintStream s): envía la información de la pila de seguimiento de excepciones al flujo de salida especificado.
  • getStackTrace(): devuelve la información de la pila de seguimiento de la excepción.
punto importante

Parece que he pasado por dos o tres anomalías, pero de hecho todavía quedan muchas cosas relacionadas con las anomalías ~

(1) Independientemente de si el bloque de código del programa está en el bloque try, o incluso el código en el bloque catch, siempre que ocurra una excepción al ejecutar el bloque de código, el sistema siempre generará automáticamente un objeto de excepción. Si el programa no define ningún bloque catch para este código, el entorno de ejecución de Java no puede encontrar el bloque catch para manejar la excepción y el programa se cerrará de forma anormal.

(2) Cuando se activa una excepción, la máquina virtual genera la excepción correspondiente y atraviesa las entradas de excepción definidas en el catch de arriba a abajo para encontrar entradas de excepción coincidentes. Al definir entradas de excepción en catch, debe seguir el principio de que solo pueden expandirse o ser irrelevantes; de lo contrario, la compilación fallará.

(3) Antes de Java 7, cada bloque catch solo podía detectar un tipo de excepción; pero a partir de Java 7, un bloque catch puede detectar múltiples tipos de excepciones. Cosas a tener en cuenta al detectar múltiples excepciones:

  • Al detectar múltiples tipos de excepciones, separe los múltiples tipos de excepción con barras verticales "|".

  • Al detectar múltiples tipos de excepciones, la variable de excepción tiene una modificación final implícita, por lo que el programa no puede reasignar la variable de excepción.

    private static void mutipleException(){
    
    
        // 多异常捕获,如下catch块可以捕获处理2种异常。
        try {
    
    
        
        }catch (ArrayIndexOutOfBoundsException|NumberFormatException e){
    
    
            e = new IllegalArgumentException("") // 编译报错,多异常不能重新赋值。
        }
        
        // 单个异常捕获
        try {
    
    
            
        }catch (Exception e){
    
    
            e = new IllegalArgumentException(""); // 编译通过,单个异常可以赋值。
        }
    }

(4) En circunstancias normales, no utilice declaraciones como return o throw en el bloque finalmente que hacen que el método termine. Una vez que se usa la declaración return o throw en el bloque finalmente, provocará las declaraciones return y throw en el intente bloquear y capturar el bloque. La declaración no es válida.

(5) Las declaraciones de retorno en try y catch, los activadores de excepciones y otros casos que hacen que el método finalice no afectarán la ejecución del bloque de código finalmente.


    public static void main(String[] args) {
    
    
        System.out.println("test return value:"+test());
    }
    
    private static int test() {
    
    
        try {
    
    
            int a = 10 / 0; //  ArithmeticException: / by zero
        } catch (Exception e) {
    
    
            return 0;
        } finally {
    
    
            System.out.println("finally");
        }
        System.out.println("test finish");
        return 1;
    }
log:

finally
test return value:0

Se puede ver que finalmente se imprime, lo que demuestra nuestro punto. Entonces, ¿por qué el valor de retorno finalmente se imprime mediante el método 0 en lugar de 1? De hecho, el proceso es así ~

Primero, el código se ejecuta hasta que el bloque try activa una ArithmeticException, y luego el bloque catch la captura y lo maneja. Sin embargo, el mecanismo de excepción tiene ese principio: si se encuentra un retorno o una excepción en el catch, eso puede causar que la función termine , entonces el código finalmente debe ejecutarse primero. El código dentro del bloque luego regresa al punto de lanzamiento o retorno en la captura. Finalmente, se ejecuta y finaliza el método de declaración catch return. El código posterior ya no se ejecutará.

También puede modificar el código para verificar. Después de ejecutar el siguiente bloque de código de captura, continuará ejecutando el código excepto intentar capturar finalmente ~

    private static int test() {
    
    
        try {
    
    
            int a = 10 / 0; //  ArithmeticException: / by zero
        } catch (Exception e) {
    
    
            System.out.println("catch");
        } finally {
    
    
            System.out.println("finally");
        }
        System.out.println("test finish");
        return 1;
    }
    
log:

catch
finally
test finish
test return value:1   

Demos una castaña para consolidarlo mejor y entenderlo a fondo emmm ~ ¿Cuál es el valor de retorno del siguiente método?

    private static int test() {
    
    
        try {
    
    
            int a = 10 / 0; //  ArithmeticException: / by zero
            return 1;
        } catch (Exception e) {
    
    
            return 2;
        } finally {
    
        
            return 3;
        }
    }

Cuando el código se ejecuta en int a of try, se activará una excepción ArithmeticException. En este momento, el controlador de excepciones la detectará y devolverá 2 en la captura. Sin embargo,Debido al mecanismo de ejecución de excepciones de JavaEn este momento, return3 in finalmente se ejecutará primero. Finalmente, aquí se encuentra la declaración de devolución y el método finaliza normalmente.

Si finalmente solo maneja algún código de cierre de recursos y no hay retorno 3 aquí, entonces el valor de retorno de este método es 2 ~

(6) A menos que el método para salir de la máquina virtual se llame en el bloque try o en el bloque catch, no importa qué código se ejecute en el bloque try o en el bloque catch o qué situación ocurra, el bloque final de manejo de excepciones siempre se ejecutará.

    private static int test() {
    
    
        try {
    
    
            int a = 10 / 0; //  ArithmeticException: / by zero
        } catch (Exception e) {
    
    
            System.out.println("catch");
            System.exit(0);
        } finally {
    
    
            System.out.println("finally");
        }
        return 0;
    }
log:

catch
Process finished with exit code 0

Como se indicó anteriormente, primero se activa la excepción ArithmeticException. En este momento, se alcanza el bloque de código de captura. Después de ejecutar la declaración de impresión, se ejecuta System.exit (0) para salir directamente de la JVM.

(7) finalmente existe el mecanismo de ejecución try catchPérdida anormalCaso

    /**
     * try中捕获异常A,catch中又触发异常B,这时finally执行完后系统只会抛出异常B。
     * 这种case也可以看做try catch的弊端,丢失了try中的异常。
     * */
    private static void test() {
    
    
        try {
    
    
            int a = 10 / 0; //  ArithmeticException: / by zero
        } catch (Exception e) {
    
    
            String a = null;
            a.length(); // finally 执行完毕后这里最终由系统抛出NullPointerException
        } finally {
    
    
            System.out.println("finally");
        }
    }
    /**
     * try中捕获异常A,catch中又触发异常B,这时finally执行又触发异常C系统只会抛出异常C。
     * 这种case也可以看做try catch的弊端,丢失了try,catch中的异常。
     */
    private static void test() {
    
    
        try {
    
    
            int a = 10 / 0; //  ArithmeticException: / by zero
        } catch (Exception e) {
    
    
            String a = null;
            a.length(); // NullPointerException
        } finally {
    
    
           Integer.parseInt("aaa"); //代码执行到这里只会抛出NumberFormatException。上述两异常忽略。
           System.out.println("finally");
        }
    }

Java 7 Excepciones suprimidas y azúcar sintáctico

Anteriormente aprendimos que las excepciones en try catch se pierden. Para resolver este problema, Java7 introdujo la excepción suprimida para resolver este problema. Esta nueva característica permite a los desarrolladores adjuntar una excepción a otra. Por lo tanto, la excepción lanzada puede ir acompañada de información de excepción múltiple.

Java 7 construyó especialmente un azúcar sintáctico llamado try-with-resources para usar automáticamente excepciones suprimidas a nivel de código de bytes. Por supuesto, el objetivo principal de esta sintaxis no es utilizar excepciones suprimidas, sino agilizar la apertura y el cierre de recursos. Porque antes de Java 7, para recursos abiertos, necesitábamos definir un bloque de código finalmente para garantizar que el recurso se pueda cerrar en condiciones de ejecución normales o anormales. Este enfoque hace que el código esté demasiado inflado ~

La sintaxis de prueba con recursos de Java 7 simplifica enormemente el código de prueba y captura finalmente. El programa puede declarar y crear una instancia de una clase que implemente la interfaz AutoCloseable después de la palabra clave try, y el compilador agregará automáticamente la operación de cierre correspondiente. En el caso de declarar varias instancias de AutoCloseable, el código de bytes compilado es similar al resultado compilado de try catch finalmente código escrito a mano. En comparación con el código manual, try-with-resources también utiliza la función de excepción suprimida para evitar que la excepción original "desaparezca".

(1) Cerrar recursos automáticamente

El sistema proporciona algunas clases que implementan la interfaz AutoCloseable. Si usa directamente la sintaxis de prueba con recursos, ya no necesitará usar finalmente para realizar el tedioso trabajo de cierre ~

    public static void main(String[] args) throws Exception {
    
    

        /**
         * 1、try()中进行变量定义(创建、赋值),类必须实现了AutoCloseable接口(或者是AutoCloseable实现类)。
         * 2、try后的代码块中可进行逻辑的操作。
         * 3、自动关闭资源的try语句相当于包含了隐式的finally块,执行了close回调,因此这个try语句可以既没有catch块,
         *    也没有finally块。
         * 4、注意AutoCloseable#close()方法抛出了Exception
         * */
        try (
                BufferedReader br = new BufferedReader(new FileReader("F://a.txt"));
                PrintStream pr = new PrintStream(new FileOutputStream("F://b.txt"))
        ) {
    
    
            br.readLine();
            pr.write("emmm".getBytes());
        }
    }

Tanto BufferedReader como PrintStream implementan indirectamente la interfaz AutoCloseable. Si los declara e inicializa en una declaración de prueba, la declaración de prueba los cerrará automáticamente. Por supuesto, también podemos personalizar la clase para implementar la interfaz e implementar el procesamiento de recursos en la interfaz. A continuación, verifique la captura de excepción ~

(2) Evitar pérdidas anormales

/**
 * Create by SunnyDay on 2022/04/22 17:37
 */
public class Demo implements AutoCloseable {
    
    

    private String desc;

    public Demo(String name) {
    
    
        this.desc = name;
    }

    public static void main(String[] args) throws Exception {
    
    
        try (
                Demo demo1 = new Demo("1");
                Demo demo2 = new Demo("2")) {
    
    

                int a = 10/0; // 执行代码 触发异常
        }
    }


    @Override
    public void close() throws Exception {
    
    
        // 这里直接抛出一个异常,验证 finally中触发了异常工作。
        throw new IllegalArgumentException();
    }
}

log: 打印所有的异常信息

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Demo.main(Demo.java:21)
	Suppressed: java.lang.IllegalArgumentException
		at Demo.close(Demo.java:29)
		at Demo.main(Demo.java:22)
	Suppressed: java.lang.IllegalArgumentException
		at Demo.close(Demo.java:29)
		at Demo.main(Demo.java:22)

Principio de implementación de excepciones

Las instancias de excepción son costosas de construir. Esto se debe a que al construir una instancia de excepción, la máquina virtual Java necesita generar un seguimiento de la pila de la excepción. Esta operación accederá a los marcos de pila Java del hilo actual uno por uno y registrará diversa información de depuración, incluido el nombre del método señalado por el marco de pila, el nombre de clase y el nombre de archivo del método, y la línea en el código. donde se activó la excepción.

Al generar un seguimiento de la pila, la máquina virtual Java ignora el constructor de excepciones y los métodos Java que llenan el marco de la pila y comienza a contar directamente desde la ubicación de la nueva excepción. Además, la máquina virtual Java ignora los marcos de pila de métodos Java que están marcados como invisibles.

Dado que la construcción de instancias de excepción es muy costosa, ¿podemos almacenar en caché las instancias de excepción y lanzarlas directamente cuando sea necesario?

Desde una perspectiva gramatical, esto está permitido. Sin embargo, el seguimiento de la pila correspondiente a esta excepción no es la ubicación de la declaración de lanzamiento, sino la ubicación de la nueva excepción. Por lo tanto, este enfoque puede inducir a error a los desarrolladores y hacer que se dirijan a la ubicación equivocada. Es por eso que, en la práctica, a menudo optamos por generar nuevas instancias de excepción.

¿Cómo implementa jvm las excepciones?

Cuando el archivo de clase se compila en código de bytes, cada método va acompañado de una tabla de excepciones. Cada tabla de excepciónuna entradarepresentarun controlador de excepciones. El procesador consta de un puntero de origen, un puntero de destino, un puntero de destino y el tipo de excepción capturada. El valor de estos punteros es el índice de código de bytes utilizado para localizar el código de bytes.

  • desde y para representar el rango de monitoreo del controlador de excepciones, es decir, el rango monitoreado por el bloque de código de prueba.
  • El objetivo representa la posición inicial del controlador de excepciones, es decir, la posición inicial de la captura.
  • El tipo de excepción es xxxException.
/**
 * Create by SunnyDay on 2022/04/22 18:45
 */
public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 异常条目1(try catch finally块就是一个异常处理器)
        try {
    
    // from
            File file = new File("F://a.txt");
            if (!file.exists()) {
    
    
                file.createNewFile();
            }
        } catch (IOException e) {
    
    //to(不包括to 可以这样记住范围“包左不包右”也即[from,to))。  target,这里也是异常处理器
                                // 开始位置
            e.printStackTrace();
        } finally {
    
    
            System.out.println("finally1");
        }

        // 异常条目2
        try {
    
    // from
             int a =  1/0;
        } catch (Exception e) {
    
    // to ,target
            e.printStackTrace();
        } finally {
    
    
            System.out.println("finally2");
        }

    }
}
//javap 命令 查看class文件:javap -c -l Test.class  main方法中生成的异常表如下:

    Exception table:
       from    to  target type
           0    22    33   Class java/io/IOException // 异常条目1
           0    22    49   any
          33    38    49   any
          60    64    75   Class java/lang/Exception // 异常条目2
          60    64    91   any
          75    80    91   any

Cuando un programa desencadena una excepción, la máquina virtual Java genera una instancia de excepción para ser lanzada y luego recorre todas las entradas en la tabla de excepciones de arriba a abajo. Cuando el valor del índice del código de bytes que desencadena la excepción está dentro del rango de monitoreo de una entrada de la tabla de excepciones, la máquina virtual Java determina si la excepción que se lanzará coincide con la excepción que la entrada desea detectar. Si hay una coincidencia, la máquina virtual Java transfiere el flujo de control al código de bytes al que apunta el puntero de destino de la entrada.

Si se recorren todas las entradas de la tabla de excepciones y la máquina virtual Java aún no coincide con un controlador de excepciones, aparecerá el marco de pila de Java correspondiente al método actual y repetirá las operaciones anteriores en la persona que llama. En el peor de los casos, la máquina virtual Java necesita atravesar la tabla de excepciones de todos los métodos en la pila Java del subproceso actual. Finalmente lanza la excepción.

La compilación del bloque de código finalmente es más complicada: la versión actual del compilador de Java copia el contenido del bloque de código finalmente y lo coloca en las salidas de todas las rutas de ejecución normales y rutas de ejecución anormales del bloque de código try-catch.

Insertar descripción de la imagen aquí
Para las rutas de ejecución de excepciones, el compilador de Java genera una o más entradas de la tabla de excepciones, monitorea todo el bloque de código try-catch y detecta todo tipo de excepciones. Los punteros de destino de estas entradas de la tabla de excepciones apuntarán a otra copia del bloque de código finalmente. Y, al final de este bloque de código finalmente, el compilador de Java volverá a generar la excepción detectada.

Si el bloque catch detecta una excepción y desencadena otra excepción, ¿qué excepción se detecta y se vuelve a lanzar finalmente? La respuesta es la última, lo que significa que se ignorará la excepción original, lo que es muy perjudicial para la depuración del código.

Controlador de excepciones no capturado

introducir

En Java, cuando un hilo lanza una excepción sin manejarla explícitamente, la JVM informará el evento de excepción al UncaughtExceptionHandler del objeto del hilo para su procesamiento. Si el hilo no establece un UncaughtExceptionHandler, la información de la pila de excepciones se enviará al terminal por defecto El programa falla directamente. Entonces, si queremos realizar algún procesamiento cuando el subproceso falla inesperadamente, podemos satisfacer las necesidades implementando UncaughtExceptionHandler.

castaña
/**
 * Create by SunnyDay on 2022/04/24 11:07
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    
    

    private volatile static CrashHandler INSTANCE;

    private CrashHandler() {
    
    
    }

    public static CrashHandler getINSTANCE() {
    
    
        if (INSTANCE == null) {
    
    
            synchronized (CrashHandler.class) {
    
    
                if (INSTANCE == null) {
    
    
                    INSTANCE = new CrashHandler();
                }
            }
        }
        return INSTANCE;
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
    
    
        printInfo(t, e);
        collectDeviceInfo();
        saveCatchInfo2File(e);
    }

    private void printInfo(Thread t, Throwable e) {
    
    
        System.out.println("异常线程:" + t.getName() + " 异常信息:" + e.getMessage());
    }

    private void collectDeviceInfo() {
    
    
        System.out.println("收集用户设备信息");
    }

    private void saveCatchInfo2File(Throwable ex) {
    
    
        System.out.println("异常信息保存到文件");
    }
}


/**
 * Create by SunnyDay on 2022/04/24 11:06
 */
public class Test {
    
    
    public static void main(String[] args) {
    
    
        getException();
        new Thread(() -> createAnException1(), "工作线程1").start();
        new Thread(() -> createAnException2(), "工作线程2").start();
    }

    /**
     * 捕获所有线程未捕获异常。
     */
    private static void getException() {
    
    
        Thread.setDefaultUncaughtExceptionHandler(CrashHandler.getINSTANCE());
    }

    /**
     * 模拟一个异常 "除0异常"
     */
    private static void createAnException1() {
    
    
        int a = 10 / 0;
    }

    /**
     * 模拟一个异常 "NumberFormatException"
     */
    private static void createAnException2() {
    
    
        Integer.parseInt("sss");
    }
}

Después de ejecutar el método Test#main anterior, el registro de CrashHandler#uncaughtException eventualmente aparecerá de la siguiente manera:

异常线程:工作线程1异常信息:/ by zero
收集用户设备信息
异常信息保存到文件

异常线程:工作线程2异常信息:For input string: "sss"
收集用户设备信息
异常信息保存到文件
Análisis de código fuente

Thread.setDefaultUncaughtExceptionHandler() se usa en la castaña anterior. De hecho, Thread también tiene un método setUncaughtExceptionHandler. Entonces, ¿cuál es la diferencia entre los dos? Primero saquemos una conclusión y luego miremos el código fuente ~

  • Thread.setDefaultUncaughtExceptionHandler (UncaughtExceptionHandler eh): se usa para establecer un controlador de excepciones global predeterminado, es decir, configurar este controlador para todos los subprocesos.
  • Thread.setUncaughtExceptionHandler (UncaughtExceptionHandler eh): configurado para que el subproceso especificado maneje excepciones no detectadas para un subproceso específico.
class Thread implements Runnable {
    
    
...
    /* The group of this thread */
    private ThreadGroup group;
    
    @FunctionalInterface
    public interface UncaughtExceptionHandler {
    
    
        void uncaughtException(Thread t, Throwable e);
    }
    
    // 单独线程的UncaughtExceptionHandler 对象
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
    // 全局的UncaughtExceptionHandler 对象
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    //设置全局的UncaughtExceptionHandler 对象
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    
    
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
    
    
            sm.checkPermission(
                new RuntimePermission("setDefaultUncaughtExceptionHandler")
                    );
        }

         defaultUncaughtExceptionHandler = eh;
     }

    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
    
    
        return defaultUncaughtExceptionHandler;
    }

    //为单独线程指定UncaughtExceptionHandler 对象
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    
    
        checkAccess();
        uncaughtExceptionHandler = eh;
    }

    /**
      uncaughtExceptionHandler对象为null则返回group。ThreadGroup 类对象在Thread构造中初始化。

      这里不妨猜测下ThreadGroup必定实现了UncaughtExceptionHandler接口。
     */
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    
    
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
...
}

En este punto, puede comprender la diferencia entre setDefaultUncaughtExceptionHandler y setUncaughtExceptionHandler. El orden en que se lanzan las excepciones cuando un hilo falla es llamar primero al getUncaughtExceptionHandler de Thread para ver si el objeto UncaughtExceptionHandler tiene un valor. Si es así, manejarlo directamente. Si no, llamar al procesamiento lógico de ThreadGroup ~

ThreadGroup es la clase de implementación predeterminada de UncaughtExceptionHandler en el JDK. GetDefaultUncaughtExceptionHandler () del hilo se llama internamente para obtener el controlador para su procesamiento. Si el controlador predeterminado no lo maneja, el proceso de excepción normal se ejecutará directamente para provocar que el programa falle ~

//这个类实现了UncaughtExceptionHandler 接口。
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    
    

    private final ThreadGroup parent;
···
    public void uncaughtException(Thread t, Throwable e) {
    
    
        if (parent != null) {
    
    //ThreadGroup 无参构造中直接parent == null。这个值一般为null。
            parent.uncaughtException(t, e);
        } else {
    
    
        /**
           ThreadGroup 其实也是调用 Thread.getDefaultUncaughtExceptionHandler()来获取UncaughtExceptionHandler 对象的。

            当我们通过setDefaultUncaughtExceptionHandler设置过UncaughtExceptionHandler对象时则调用
            UncaughtExceptionHandler#uncaughtException
            当未设置过UncaughtExceptionHandler对象时打印异常信息,后续就是jvm的crash了~
         */
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
    
    
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
    
    
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
···
}

Fin

Referencia: Desmontaje en profundidad de la máquina virtual Java

Referencia: Análisis de problemas relacionados con UncaughtExceptionHandler

Supongo que te gusta

Origin blog.csdn.net/qq_38350635/article/details/124325091
Recomendado
Clasificación