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

Vert.x遗留服务OOM无法自动生成堆转储问题咨询

问题分析与解决方案

一、当前代码的错误处理问题

  1. 冗余的Future管理:代码手动创建了独立的result CompletableFuture,再通过thenAccept和exceptionally完成它,这种写法完全没必要,可直接利用CompletableFuture的链式调用简化逻辑,避免手动维护Future实例的潜在风险。
  2. 错误捕获范围不合理:exceptionally捕获了所有Throwable(包括Error类型),而OutOfMemoryError属于致命Error,这类错误本应交给JVM处理——只有当Error未被捕获时,JVM才会触发-XX:+HeapDumpOnOutOfMemoryError生成堆转储。当前代码将OOM Error仅做日志记录后吞掉,导致自动堆转储失效。

二、无需修改200多处exceptionally的简便方案

方案1:修改日志工具的rcCatching方法

在LOGGER.rcCatching中添加判断逻辑:如果传入的异常是Error类型,直接重新抛出。这样既保留了日志记录,又能让JVM感知到致命Error,触发自动堆转储。示例代码:

public void rcCatching(RuntimeContext rc, Throwable th) {
    // 原有日志输出逻辑
    log.error("catching - throwable message: {}", th.getMessage(), th);
    // 新增:致命Error重新抛出,交给JVM处理
    if (th instanceof Error) {
        throw (Error) th;
    }
}

该方案仅需修改日志工具的一处代码,无需触碰200多个业务代码中的exceptionally。

方案2:字节码增强(AspectJ/ByteBuddy)

通过字节码增强工具拦截所有CompletableFuture.exceptionally的调用,当处理的异常是Error时,直接抛出不执行原有逻辑。以AspectJ为例,切面代码如下:

@Aspect
public class CompletableFutureErrorAspect {
    @Around("call(java.util.concurrent.CompletableFuture java.util.concurrent.CompletableFuture.exceptionally(java.util.function.Function)) && args(function)")
    public Object interceptExceptionally(ProceedingJoinPoint joinPoint, Function<Throwable, ?> function) throws Throwable {
        return ((CompletableFuture<?>) joinPoint.proceed()).exceptionally(th -> {
            if (th instanceof Error) {
                throw (Error) th;
            }
            return function.apply(th);
        });
    }
}

这种方式完全无需修改业务代码,仅需引入AspectJ依赖并配置切面即可生效。

方案3:临时JVM参数兜底

如果上述方案暂时无法实施,可添加-XX:+ExitOnOutOfMemoryError参数,让JVM在OOM时强制退出,配合-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath指定存储路径,确保JVM退出前生成堆转储。注意:该参数会直接终止服务,需评估业务影响后使用。

三、单段代码的优化建议(可选)

针对你提供的代码片段,优化后可去掉手动维护的result,用链式调用简化逻辑,同时调整异常处理范围,仅捕获Exception不处理Error:

protected CompletableFuture<RenderingContext> loadContentFormat(RenderingContext ctx) {
    final RuntimeContext rc = ctx.getRuntimeContext();
    LOGGER.rcEntry(rc, ctx.getId());
    
    final String cacheKey = getCacheKey(rc, "content", ctx.getId());

    LOGGER.rcTrace(rc, deliverySearchLRUCache.get(cacheKey) != null ? "cache hit for content by id {}" : "cachemiss for content by id {}", cacheKey);

    return deliverySearchLRUCache.computeIfAbsent(cacheKey, (k) -> 
            dataAccessor.searchSingleDeliveryItemDocumentById(rc, "content", ctx.getId(), ctx.isRestrictedInvocation()))
        .thenApply(deliveryItemJson -> {
            if (deliveryItemJson == null) {
                throw new ItemNotFoundException("content", ctx.getId());
            }
            LOGGER.debug("Loaded content: {}",  StringUtil.truncateObject(deliveryItemJson, 512));
            ctx.setItemJson(deliveryItemJson);
            return ctx;
        })
        .exceptionally(th -> {
            // 仅处理业务Exception,Error直接抛出给JVM
            if (th instanceof Exception) {
                LOGGER.rcCatching(rc, th);
                throw (Exception) th;
            }
            throw (Error) th;
        });
}

内容的提问来源于stack exchange,提问作者james

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:54:54