You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中@ControllerAdvice无法捕获OutOfMemoryError的解决方案咨询

Spring Boot中捕获根因为OutOfMemoryError的异常并记录内存信息

你原来的@ControllerAdvice没生效有几个核心原因:

  1. OutOfMemoryError是Error而非Exception,默认@ExceptionHandler只处理Exception子类,除非显式指定处理Throwable或Error;
  2. 若OOM发生在非请求处理线程(比如定时任务、自定义线程池),@ControllerAdvice仅能覆盖DispatcherServlet处理请求的链路,管不到这些场景;
  3. 当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 20:56:38