Java 中如何记录未捕获的异常?
Java 记录未捕获异常:实现 Thread.UncaughtExceptionHandler 接口并设为默认处理器,逃出线程 run() 的异常可触发处理器,Future 保存的异常需另行观察。本文给出完整实现。
核心:实现 Thread.UncaughtExceptionHandler 接口,在 uncaughtException() 方法中用日志框架记录异常,然后通过 Thread.setDefaultUncaughtExceptionHandler() 注册为全局默认处理器。
完整实现
import java.util.logging.Level;
import java.util.logging.Logger;
public class GlobalExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final Logger LOGGER = Logger.getLogger(GlobalExceptionHandler.class.getName());
@Override
public void uncaughtException(Thread t, Throwable e) {
LOGGER.log(Level.SEVERE, "线程 " + t.getName() + " 中出现未捕获异常", e);
}
}
在程序入口注册:
public class Main {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(new GlobalExceptionHandler());
// 正常业务逻辑...
}
}
此后逃出线程 run() 的异常按该线程的处理器规则处理;线程专属处理器或 ThreadGroup 可优先于默认处理器,框架内部捕获的异常不会因此进入默认处理器。
对单个线程设置
Thread thread = new Thread(() -> {
throw new RuntimeException("出错了");
});
thread.setUncaughtExceptionHandler((t, e) -> {
System.err.println("线程 " + t.getName() + " 异常: " + e.getMessage());
});
thread.start();
线程池中的注意事项
ThreadPoolExecutor 的 execute() 提交的 Runnable 抛出的异常会走 UncaughtExceptionHandler;但 submit() 返回的 Future 中的异常不会——它被封装在 Future 里,调用 get() 时才抛出。
常见问题(FAQ)
Q:Spring Boot 项目需要手动注册吗?
A:Spring Boot 的 @ControllerAdvice + @ExceptionHandler 处理 Controller 层的异常;非 Web 层的异常仍建议注册全局 UncaughtExceptionHandler。
Q:捕获后进程会退出吗?
A:发生异常的线程终止;当所有已启动的非守护线程结束后,JVM 才开始关闭。main 线程异常时若仍有其他非守护线程运行,进程不会仅因此立即退出。
Q:log4j2/SLF4J 怎么用?
A:把上面示例中的 java.util.logging 替换为你项目使用的日志框架即可,逻辑不变。