Spring Boot中@ControllerAdvice无法捕获OutOfMemoryError的解决方案咨询
Spring Boot中捕获根因为OutOfMemoryError的异常并记录内存信息
你原来的@ControllerAdvice没生效有几个核心原因:
OutOfMemoryError是Error而非Exception,默认@ExceptionHandler只处理Exception子类,除非显式指定处理Throwable或Error;- 若OOM发生在非请求处理线程(比如定时任务、自定义线程池),
@ControllerAdvice仅能覆盖DispatcherServlet处理请求的链路,管不到这些场景; - 当OOM被包装为其他异常作为根因时,栈顶异常不是OOM,直接匹配
OutOfMemoryError.class的处理器不会触发。
下面提供两种互补的解决方案,覆盖所有场景:
方案一:覆盖请求链路中的异常(含包装后的OOM)
用@RestControllerAdvice配合@ExceptionHandler(Throwable.class)捕获所有请求链路中的异常,再遍历异常链找到根因判断是否为OOM:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalRequestErrorHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalRequestErrorHandler.class); @ExceptionHandler(Throwable.class) public ResponseEntity<String> handleAllRequestExceptions(Throwable ex) { // 遍历异常链获取根因 Throwable rootCause = getRootCause(ex); if (rootCause instanceof OutOfMemoryError) { // 记录当前内存状态 Runtime runtime = Runtime.getRuntime(); long freeMem = runtime.freeMemory(); long totalMem = runtime.totalMemory(); long maxMem = runtime.maxMemory(); logger.error("请求链路触发OutOfMemoryError: 剩余内存={}B, 已分配内存={}B, 最大可用内存={}B", freeMem, totalMem, maxMem, rootCause); // 返回自定义错误响应 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("系统内存资源不足,请联系管理员"); } // 其他异常的默认处理 logger.error("请求处理发生异常", ex); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("系统异常,请稍后重试"); } // 工具方法:循环获取异常根因 private Throwable getRootCause(Throwable throwable) { Throwable cause; while ((cause = throwable.getCause()) != null) { throwable = cause; } return throwable; } }
方案二:处理全局所有线程的未捕获异常(含后台线程OOM)
如果OOM发生在非请求线程(比如定时任务、自定义线程),需要设置JVM全局未捕获异常处理器,覆盖这类场景:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class YourApplication { public static void main(String[] args) { // 设置全局未捕获异常处理器 Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { Throwable rootCause = getRootCause(throwable); if (rootCause instanceof OutOfMemoryError) { Runtime runtime = Runtime.getRuntime(); long freeMem = runtime.freeMemory(); long totalMem = runtime.totalMemory(); long maxMem = runtime.maxMemory(); LoggerFactory.getLogger(YourApplication.class).error( "线程[{}]触发OutOfMemoryError: 剩余内存={}B, 已分配内存={}B, 最大可用内存={}B", thread.getName(), freeMem, totalMem, maxMem, rootCause); } else { LoggerFactory.getLogger(YourApplication.class).error( "线程[{}]发生未捕获异常", thread.getName(), throwable); } }); SpringApplication.run(YourApplication.class, args); } private static Throwable getRootCause(Throwable throwable) { Throwable cause; while ((cause = throwable.getCause()) != null) { throwable = cause; } return throwable; } }
注意事项
- 记录内存信息时,尽量避免创建新对象(防止OOM加剧),直接使用
Runtime的原生方法即可; - 如果需要在OOM后优雅关闭应用,可以在处理逻辑中添加
System.exit(1)(但要确保必要资源已释放); - 两种方案配合使用,可覆盖所有可能出现OOM的场景。
内容的提问来源于stack exchange,提问作者Tahjid Ashfaque
相关产品推荐
相关产品推荐

