spring boot 全局异常处理无法通过 @controlleradvice 捕获栈溢出异常,因为 stackoverflowerror 是 error 的子类,而 spring mvc 的异常处理器默认只拦截 exception。要捕获它,必须在应用启动时注册一个自定义的全局未捕获异常处理器(uncaught exception handler),在 jvm 线程终止前接管错误现场。

为什么 @ControllerAdvice 抓不到 StackOverflowError
Spring Boot 的 @RestControllerAdvice 或 @ControllerAdvice 内部依赖 HandlerExceptionResolver 链,该机制仅针对 HTTP 请求处理过程中抛出的 Exception 及其子类生效。当递归过深导致栈空间耗尽时,JVM 会抛出 java.lang.StackOverflowError,这属于 java.lang.Error 体系,直接中断当前线程的执行流程,不会进入 Spring 的异常解析器逻辑。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// 注册全局未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
if (throwable instanceof StackOverflowError) {
System.err.println("捕获到栈溢出: " + thread.getName());
throwable.printStackTrace();
} else {
System.err.println("其他未捕获异常: " + throwable.getMessage());
}
});
}
}
如何区分不同类型的 Error 并记录日志
在全局未捕获异常处理器中,应当对 Error 进行类型判断,以便区分栈溢出、内存溢出或其他严重错误。通常的做法是在 catch 块中检查异常实例类型,并记录详细的堆栈信息。对于栈溢出,可以额外记录当前线程的状态或尝试获取部分堆栈跟踪,但要注意避免在错误处理中再次引发栈溢出。

import java.io.PrintWriter;
import java.io.StringWriter;
public class GlobalExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
if (e instanceof StackOverflowError) {
System.err.println("[FATAL] Stack Overflow in thread: " + t.getName());
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
System.err.println(sw.toString());
} else if (e instanceof OutOfMemoryError) {
System.err.println("[FATAL] Out of Memory in thread: " + t.getName());
e.printStackTrace();
} else {
System.err.println("[UNEXPECTED] Uncaught exception in thread: " + t.getName());
e.printStackTrace();
}
}
}
基于递归深度限制的具体问题
在实际开发中,与其依赖运行时捕获栈溢出,不如在代码层面限制递归深度或使用迭代替代递归。例如,在处理树形结构数据时,可以引入一个计数器来限制递归层数,超过阈值则抛出自定义异常或返回部分结果。这样可以避免 JVM 级别的错误,使程序行为更可控。











