在java中,Exception分为两种:RuntimeException和其他需要显式捕获的异常,例如在创建文件时,FileNotFoundException 必须 使用try/catch捕获,而RuntimeException是不需要捕获的,最常见的可能就是NullPointerException。
想想一下,在线上环境中,一个线程突然抛出了一个RuntimeException而没有捕获,然后线程就挂了。由于异常信息会打印到控制台,所以日志中并不会有报错的日志。这种情况下,这个bug怎么排查呢?做线下模拟,把可能报错的地方用try/catch包围起来再上线一次,而不确定真正的bug是否修复了。
java线程中,提供了UncaughtExceptionHandler来捕获一切未捕获的异常。码中写上:
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
实现如下:
class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
StackTraceElement[] stackTrace = e.getStackTrace();
System.out.printf("[%s] %s \r\n", t.getName(), e);
for (StackTraceElement ste : stackTrace) {
System.out.printf(" at %s.%s(%s:%d)\r\n",
ste.getClassName(), ste.getMethodName(),
ste.getFileName(), ste.getLineNumber());
}
}
}
线上应用中,将MyUncaughtExceptionHandler中的堆栈打印换成日志logger.error(msg, e)打印就可以。
以下是在线程池中执行任务时,出现整数除0打印的异常:
[MyPool1-Thread1] java.lang.ArithmeticException: / by zero
at com.reign.thread.Task.run(ThreadPoolTest.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
总结
使用UncaughtExceptionHandler打印异常信息,可以将程序中未捕获的异常堆栈打印出来,方便后续处理定位解决bug。另外,线程抛出异常时,已经挂掉了,UncaughtExceptionHandler只是做一些异常的记录和崩溃的统计,并不能拯救抛出异常的线程。