一篇文章彻底理解 Java 的 Suppressed exceptions 机制

开发 前端
在查看 JAVA 应用抛出的异常堆栈以排查问题时,我们有时会看到所谓 suppressed exceptions,即被抑制的异常。理解 suppressed exceptions 的原理,对我们分析问题的底层真实原因大有裨益。所以本文分析总结下 Java 中的 suppressed exceptions。

1. 前言

在查看 JAVA 应用抛出的异常堆栈以排查问题时,我们有时会看到所谓 suppressed exceptions,即被抑制的异常。理解 suppressed exceptions 的原理,对我们分析问题的底层真实原因大有裨益。所以本文分析总结下 Java 中的 suppressed exceptions。

2. suppressed exceptions 机制总结

  • 简单来说,suppressed exceptions 是 JVM 中一个真实发生了的异常,但由于某些原因被 JVM 忽略/抑制了;
  • 一个常见的异常被忽略/抑制的场景是 try-catch-finally 代码块:由于无论 try 代码块是否正常执行结束,finally 代码块都会执行,所以如果 try 代码块和 finally 代码块都抛出异常时,为在打印的异常堆栈中完整还原异常现场,代码中可以做特殊处理(具体的处理方式见后文),以将两个异常都打印,并标记 try 中的异常为 suppressed;(用户需要对异常代码做处理);
  • 另一个常见的异常被忽略的场景是 try-with-resources 代码块:java7 引进了 try-with-resources 代码块和 AutoCloseable 接口来管理资源,当 try-with-resources 底层的业务逻辑代码执行完毕时,无论其执行是否正常结束,jvm 都会自动关闭 try 中指定的 AutoCloseable 资源,以避免资源泄露,如果业务逻辑代码的处理和 AutoCloseable 资源的关闭都发生了异常,此时 jvm 会将两个异常都打印,并标记关闭 AutoCloseable 资源触发的异常为try 中的异常为 suppressed;(用户不用做特殊处理);
  • 所以,为有效利用 suppressed exceptions 机制妥善打印异常堆栈以辅助问题排查,从 Java 7 开始, 我们可以使用 Throwable 类的如下方法来处理 suppressed exceptions: 即 java.lang.Throwable#addSuppressed 和java.lang.Throwable#getSuppressed
  • A suppressed exception is an exception that is thrown but somehow ignored;
  • A common scenario for this is the try-catch-finally block: when the finally block throws an exception,any exception originally thrown in the try block is then suppressed;
  • Another common scenario is the try-with-resources block:Java 7 introduced the try-with-resources construct and the AutoCloseable interface for resource management,when exception occurs both in the business processing and resource closing,it’s the exception thrown in the close method that’s suppressed;
  • Starting with Java 7, we can now use two methods on the Throwable class to handle our suppressed exceptions: addSuppressed and getSuppressed.

3 suppressed exceptions 机制 细节- try-catch-finally 代码块

  • 当 finally 代码块没有使用 java.lang.Throwable#addSuppressed 对异常进行特殊处理时,如果 try 代码块和 finally 代码块都抛出异常,打印的异常堆栈的示例如下,可以看到,没有打印try 中的异常,而仅仅打印了 finally 中的异常,此时用户显然无法轻易获知异常的真实原因;
java.lang.NullPointerException
at com.keep.bdata.SuppressedExceptionsDemo.demoExceptionWithNoSuppress(SuppressedExceptionsDemo.java:21)
at com.keep.bdata.SuppressedExceptionsDemo.givenNonExistentFileName_whenAttemptFileOpen_thenNullPointerException(SuppressedExceptionsDemo.java:12)

图片图片

  • 当 finally 代码块使用 java.lang.Throwable#addSuppressed 对异常进行了特殊处理时,如果 try 代码块和 finally 代码块都抛出异常,打印的异常堆栈的示例如下,可以看到,try 中的异常和 finally 中的异常都被打印了,且 try 中的异常被标记为 suppressed exceptions, 如果用户理解 suppressed exceptions 的机制,通过这些异常堆栈,显然可以轻松获知异常的真实原因;
java.lang.NullPointerException
	at com.keep.bdata.SuppressedExceptionsDemo.demoExceptionWithSuppressed(SuppressedExceptionsDemo.java:38)
	at com.keep.bdata.SuppressedExceptionsDemo.givenNonExistentFileName_whenAttemptFileOpen_thenNullPointerException_withSuppressed(SuppressedExceptionsDemo.java:27)
	Suppressed: java.io.FileNotFoundException: \non-existent-path\non-existent-file.txt (系统找不到指定的路径。)
		at java.io.FileInputStream.open0(Native Method)
		at java.io.FileInputStream.open(FileInputStream.java:195)
		at java.io.FileInputStream.<init>(FileInputStream.java:138)
		at java.io.FileInputStream.<init>(FileInputStream.java:93)
		at com.keep.bdata.SuppressedExceptionsDemo.demoExceptionWithSuppressed(SuppressedExceptionsDemo.java:33)

图片图片

4 suppressed exceptions 机制 细节 - try-with-resources 代码块

  • java7 引进了 try-with-resources 代码块和 AutoCloseable 接口来管理资源,当 try-with-resources 底层的业务逻辑代码执行完毕时,无论其执行是否正常结束,jvm 都会自动关闭 try 中指定的 AutoCloseable 资源,以避免资源泄露;
  • 如果业务逻辑代码的处理和 AutoCloseable 资源的关闭都发生了异常,此时 jvm 会将两个异常都打印,并标记关闭 AutoCloseable 资源触发的异常为try 中的异常为 suppressed,打印的异常堆栈的示例如下,如果用户理解 suppressed exceptions 的机制,通过这些异常堆栈,显然可以轻松获知异常的真实原因;
  • 注意这是jvm自己实现的,用户不需要对代码做特殊处理;
java.lang.IllegalArgumentException: Thrown from processSomething()
	at com.keep.bdata.TryWithResourceDemo$ExceptionalResource.processSomething(TryWithResourceDemo.java:23)
	at com.keep.bdata.TryWithResourceDemo.demoExceptionalResource(TryWithResourceDemo.java:17)
	at com.keep.bdata.TryWithResourceDemo.givenNonExistentFileName_whenAttemptFileOpen_thenNullPointerException_suppressed(TryWithResourceDemo.java:12)
	Suppressed: java.lang.NullPointerException: Thrown from close()
		at com.keep.bdata.TryWithResourceDemo$ExceptionalResource.close(TryWithResourceDemo.java:28)
		at com.keep.bdata.TryWithResourceDemo.demoExceptionalResource(TryWithResourceDemo.java:18)

图片图片

5 suppressed exceptions 机制完整示例代码

  • suppressed exceptions 机制的完整示例代码如下(try-catch-finally ):
package com.keep.bdata;

import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

publicclass SuppressedExceptionsDemo {
    @Test
    public void givenNonExistentFileName_whenAttemptFileOpen_thenNullPointerException() throws IOException {
        demoExceptionWithNoSuppress("/non-existent-path/non-existent-file.txt");
    }
    public static void demoExceptionWithNoSuppress(String filePath) throws IOException {
        FileInputStream fileIn = null;
        try {
            fileIn = new FileInputStream(filePath);
        } catch (FileNotFoundException e) {
            thrownew IOException(e);
        } finally {
            fileIn.close();
        }
    }

    @Test
    public void givenNonExistentFileName_whenAttemptFileOpen_thenNullPointerException_withSuppressed() throws IOException{
        demoExceptionWithSuppressed("/non-existent-path/non-existent-file.txt");
    }
    public static void demoExceptionWithSuppressed(String filePath) throws IOException {
        Throwable firstException = null;
        FileInputStream fileIn = null;
        try {
            fileIn = new FileInputStream(filePath);
        } catch (IOException e) {
            firstException = e;
        } finally {
            try {
                fileIn.close();
            } catch (NullPointerException npe) {
                if (firstException != null) {
                    npe.addSuppressed(firstException);
                }
                throw npe;
            }
        }
    }
}
  • suppressed exceptions 机制的完整示例代码如下(try-with-resources 完整示例代码):
package com.keep.bdata;

import org.junit.jupiter.api.Test;
publicclass TryWithResourceDemo  {
    @Test
    public void givenNonExistentFileName_whenAttemptFileOpen_thenNullPointerException_suppressed() throws Exception {
        demoExceptionalResource();
    }
    public void demoExceptionalResource() throws Exception {
        try (ExceptionalResource exceptionalResource = new ExceptionalResource()) {
            exceptionalResource.processSomething();
        }
    }
    class ExceptionalResource implements AutoCloseable {
        public void processSomething() {
            thrownew IllegalArgumentException("Thrown from processSomething()");
        }
        @Override
        public void close() throws Exception {
            thrownew NullPointerException("Thrown from close()");
        }
    }


责任编辑:武晓燕 来源: 明哥的IT随笔
相关推荐

2017-07-20 16:55:56

Android事件响应View源码分析

2024-06-25 08:18:55

2021-04-07 13:28:21

函数程序员异步

2013-04-15 10:59:08

iOS开发ARC版本说明

2022-01-05 10:22:17

HiveAuthenticat认证

2015-07-15 17:09:48

HiveHadoop分布式文件系统

2019-07-23 08:55:46

Base64编码底层

2020-10-09 08:15:11

JsBridge

2023-05-08 08:21:15

JavaNIO编程

2021-07-01 10:01:16

JavaLinkedList集合

2024-01-30 09:31:53

SQL语言数据库

2024-05-10 08:19:59

arthasjava字节码

2020-12-29 05:35:43

FlinkSQL排序

2017-09-05 08:52:37

Git程序员命令

2021-06-30 00:20:12

Hangfire.NET平台

2022-02-21 09:44:45

Git开源分布式

2023-05-12 08:19:12

Netty程序框架

2019-04-17 15:16:00

Sparkshuffle算法

2021-04-09 08:40:51

网络保险网络安全网络风险

2024-02-28 12:41:00

源码内核参数
点赞
收藏

51CTO技术栈公众号