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

无需修改编译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参数并调整实现方式。

前置配置

  1. 添加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>
  1. 添加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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 06:50:38