import java.lang.instrument.Instrumentation; import java.security.Permission; /** * 极小 javaagent:拦 System.exit,把调用栈打出来。 * * 目的:3.8.99 编辑器在"必须用捆绑运行时"那道检查后会退出, * 拿到 checkExit 的调用栈就知道检查写在哪、校验的是什么,不用再猜。 * * 用法:-javaagent:rea-agent.jar */ public final class ReaAgent { public static void premain(String arguments, Instrumentation instrumentation) { System.err.println("[rea-agent] premain 已加载"); // JVM 退出时把主线程等的位置打出来。进程是"静默退出"还是"崩在别处", // 看一次栈就够,不用再猜。 Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { System.err.println("[rea-agent] JVM 正在退出,当前线程栈:"); for (java.util.Map.Entry entry : Thread.getAllStackTraces().entrySet()) { Thread thread = entry.getKey(); if (!thread.isAlive()) continue; System.err.println("[rea-agent] 线程 " + thread.getName() + (thread.isDaemon() ? " (daemon)" : "")); for (StackTraceElement frame : entry.getValue()) { System.err.println("[rea-agent] " + frame); } } } }); Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable error) { System.err.println("[rea-agent] 未捕获异常,线程 " + thread.getName()); error.printStackTrace(System.err); } }); System.setSecurityManager(new SecurityManager() { @Override public void checkPermission(Permission permission) { // 全部放行,只观察。 } @Override public void checkExit(int status) { Throwable trace = new Throwable("checkExit(" + status + ")"); StringBuilder builder = new StringBuilder("[rea-agent] System.exit(" + status + ") 调用栈:"); for (StackTraceElement frame : trace.getStackTrace()) { builder.append("\n ").append(frame); } System.err.println(builder.toString()); // 不抛异常 = 允许退出,这样行为和原来一致。 } }); } }