Vert.x遗留服务OOM无法自动生成堆转储问题咨询
问题分析与解决方案
一、当前代码的错误处理问题
- 冗余的Future管理:代码手动创建了独立的
resultCompletableFuture,再通过thenAccept和exceptionally完成它,这种写法完全没必要,可直接利用CompletableFuture的链式调用简化逻辑,避免手动维护Future实例的潜在风险。 - 错误捕获范围不合理:
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
相关产品推荐
相关产品推荐

