如何重写Java标准OutOfMemoryError以发送带服务器ID的告警?
如何在OutOfMemoryError发生时自动发送含服务器ID的告警
Java中无法直接"重写"标准的OutOfMemoryError类(它属于JDK核心类库,类加载机制限制了无法替换或修改其原生实现),但可以通过以下几种方案实现OOM发生时自动触发告警:
方案1:全局未捕获异常处理器
针对线程中抛出的OOM,可通过设置Thread.UncaughtExceptionHandler全局处理器捕获,但无法覆盖JVM内部直接抛出的所有OOM场景。
public class OomAlertHandler implements Thread.UncaughtExceptionHandler { private static final String SERVER_ID = "SERVER-001"; // 服务器ID可从配置文件读取 @Override public void uncaughtException(Thread t, Throwable e) { if (e instanceof OutOfMemoryError) { sendAlert("OOM发生在线程: " + t.getName() + ", 服务器ID: " + SERVER_ID); } // 保留原有异常处理逻辑 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e); } // 告警发送逻辑必须轻量化,避免额外内存分配 private void sendAlert(String message) { try (Socket socket = new Socket("alert-server", 9999)) { OutputStream out = socket.getOutputStream(); out.write(message.getBytes(StandardCharsets.UTF_8)); out.flush(); } catch (Exception ex) { // 告警失败时不抛出新异常,避免加重内存负担 ex.printStackTrace(System.err); } } } // 应用启动时初始化 public class AppBootstrap { public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler(new OomAlertHandler()); // 启动业务逻辑 } }
方案2:Java Agent拦截OOM实例化
通过Java Instrumentation机制,在JVM加载OutOfMemoryError类时修改其构造方法,注入告警逻辑。这种方式能覆盖绝大多数OOM场景,包括JVM内部抛出的情况。
import java.lang.instrument.Instrumentation; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.io.OutputStream; public class OomAlertAgent { private static final String SERVER_ID = "SERVER-001"; public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> { if ("java/lang/OutOfMemoryError".equals(className)) { // 触发告警,实际生产建议用ByteBuddy等字节码库修改构造方法逻辑 sendAlert("OOM即将抛出,服务器ID: " + SERVER_ID); } return classfileBuffer; }); } private static void sendAlert(String message) { try (Socket socket = new Socket("alert-server", 9999)) { OutputStream out = socket.getOutputStream(); out.write(message.getBytes(StandardCharsets.UTF_8)); out.flush(); } catch (Exception ex) { ex.printStackTrace(System.err); } } }
将上述代码打包为Agent Jar后,在应用启动时添加参数:-javaagent:oom-alert-agent.jar
方案3:内存监控+Shutdown Hook
定时监控JVM内存使用率,达到阈值时提前告警;同时注册Shutdown Hook,在JVM因OOM退出时尝试发送告警。
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.io.OutputStream; public class MemoryMonitor { private static final String SERVER_ID = "SERVER-001"; private static final double MEMORY_THRESHOLD = 0.9; // 内存使用率阈值90% public static void startMonitoring() { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(() -> { Runtime runtime = Runtime.getRuntime(); long usedMemory = runtime.totalMemory() - runtime.freeMemory(); double usageRate = (double) usedMemory / runtime.maxMemory(); if (usageRate >= MEMORY_THRESHOLD) { sendAlert("内存使用率触发阈值: " + String.format("%.2f%%", usageRate*100) + ", 服务器ID: " + SERVER_ID); } }, 0, 5, TimeUnit.SECONDS); // 注册JVM退出钩子 Runtime.getRuntime().addShutdownHook(new Thread(() -> { sendAlert("JVM即将退出,疑似OOM触发,服务器ID: " + SERVER_ID); })); } private static void sendAlert(String message) { try (Socket socket = new Socket("alert-server", 9999)) { OutputStream out = socket.getOutputStream(); out.write(message.getBytes(StandardCharsets.UTF_8)); out.flush(); } catch (Exception ex) { ex.printStackTrace(System.err); } } } // 应用启动时调用 MemoryMonitor.startMonitoring();
关键注意事项
- 告警逻辑必须轻量化:OOM发生后JVM内存极度紧张,告警代码要避免创建大对象、避免使用依赖大量内存的框架(如Spring、Logback等),尽量用原生Java API实现。
- 极端场景无法覆盖:如果OOM导致JVM立即崩溃,所有代码都无法执行,这种情况只能依赖外部监控工具(如Prometheus+Grafana、JVM监控平台)补充覆盖。
- 提前测试验证:可通过
-Xmx参数设置小内存,编写测试代码模拟OOM,验证告警逻辑是否生效。
内容的提问来源于stack exchange,提问作者user1607416
相关产品推荐
相关产品推荐

