无需修改编译JDK类,如何追踪System.gc调用方?
无需修改JDK即可追踪System.gc()调用方的方案
可以通过字节码增强工具(比如ByteBuddy)在运行时改写Runtime.gc()的实现逻辑,无需修改或重编译JDK源码。Java 8和Java 17因模块系统差异,实现细节有所不同,具体方案如下:
Java 8 实现方案
依赖准备
添加ByteBuddy的Maven依赖:
<dependency> <groupId>com.googlecode.byte-buddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.14.12</version> </dependency>
代码实现
import net.bytebuddy.ByteBuddy; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.MethodCall; import net.bytebuddy.matcher.ElementMatchers; public class GCTracker { public static void main(String[] args) { // 启动ByteBuddy代理 ByteBuddyAgent.install(); try { new ByteBuddy() .redefine(Runtime.class) .method(ElementMatchers.named("gc")) // 先执行拦截逻辑,再调用原方法 .intercept(MethodDelegation.to(GCTrackerInterceptor.class) .andThen(MethodCall.invokeSuper())) .make() .load(Runtime.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); // 模拟调用测试 System.gc(); } catch (Exception e) { e.printStackTrace(); } } public static class GCTrackerInterceptor { public static void trackCall() { System.err.println("=== System.gc() 被调用 ==="); // 打印调用栈 Thread.dumpStack(); } } }
说明
Java 8允许直接重定义java.lang.Runtime类,通过ByteBuddy的类重加载机制替换gc()方法,拦截后打印调用栈,再执行原方法逻辑。
Java 17 实现方案
Java 17引入模块系统后,java.base模块下的类默认禁止修改,需要额外配置JVM参数并调整实现方式。
前置配置
- 添加Maven依赖(需包含ByteBuddy Agent):
<dependency> <groupId>com.googlecode.byte-buddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.14.12</version> </dependency> <dependency> <groupId>com.googlecode.byte-buddy</groupId> <artifactId>byte-buddy-agent</artifactId> <version>1.14.12</version> </dependency>
- 添加JVM启动参数:
--add-opens java.base/java.lang=ALL-UNNAMED -javaagent:/path/to/byte-buddy-agent-1.14.12.jar
注:需将/path/to/替换为本地ByteBuddy Agent jar的实际路径
代码实现
import net.bytebuddy.ByteBuddy; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.MethodCall; import net.bytebuddy.matcher.ElementMatchers; import java.util.stream.Stream; public class GCTracker17 { public static void main(String[] args) { ByteBuddyAgent.install(); try { new ByteBuddy() .redefine(Runtime.class) .method(ElementMatchers.named("gc")) .intercept(MethodDelegation.to(GCTrackerInterceptor17.class) .andThen(MethodCall.invokeSuper())) .make() .load(Runtime.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); // 模拟调用测试 System.gc(); } catch (Exception e) { e.printStackTrace(); } } public static class GCTrackerInterceptor17 { public static void trackCall() { System.err.println("=== System.gc() 被调用 ==="); // 使用Java 17新增的StackWalker高效获取调用栈 StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(GCTracker17::printCallStack); } private static Stream<StackWalker.StackFrame> printCallStack(Stream<StackWalker.StackFrame> frameStream) { frameStream.skip(2) // 跳过拦截方法和原方法调用的栈帧 .forEach(frame -> System.err.printf("\tat %s.%s(%s:%d)%n", frame.getClassName(), frame.getMethodName(), frame.getFileName(), frame.getLineNumber())); return frameStream; } } }
说明
- 必须通过
--add-opens参数开放java.lang包给未命名模块,否则无法修改Runtime类。 - 使用Java 17新增的
StackWalker替代Thread.dumpStack(),能更精准地过滤栈帧、获取调用信息。
为什么不直接用反射?
System.gc()本质是调用Runtime.getRuntime().gc(),而Runtime.gc()是native方法,反射只能调用方法,无法修改方法的实现逻辑,因此必须依赖字节码增强工具来替换方法行为。
内容的提问来源于stack exchange,提问作者javando
相关产品推荐
相关产品推荐

