Mmc秒杀圈百科 

Instrumentation

由 朝武芳乃管理员 最后编辑于 2026-10-04 02:03 · 当前版本 v3
标签:技术agent正经内容
你尚未登录。登录后可编辑本页与创建新条目 —— 登录 或 注册账号。

本条基于 Java 17(Minecraft 1.20.1 所用版本)。

Instrumentation(java.lang.instrument.Instrumentation)是 JavaAgent 修改类的标准接口,底层由 JVMTI 实现,JDK 中的具体实现类是 InstrumentationImpl。

获取

  • 启动时:-javaagent:xxx.jar,jar 清单声明 Premain-Class,JVM 调用 premain(String, Instrumentation)。
  • 运行时附加:通过 Attach API(VirtualMachine.attach(pid).loadAgent(jar)),清单声明 Agent-Class,JVM 调用 agentmain。Java 9 起默认不允许附加到自身进程(需 -Djdk.attach.allowAttachSelf=true),常用的绕过方式见下文“运行时外部进程附加”。
  • 重转换 / 重定义还需清单声明 Can-Retransform-Classes / Can-Redefine-Classes。

运行时外部进程附加

目前最常用的做法。Java 9 起 JVM 默认不允许附加到自身进程,所以由模组另起一个 Java 子进程,让子进程附加到游戏进程并加载 agent。整个过程不需要任何启动参数(既不用 -javaagent,也不用 -Djdk.attach.allowAttachSelf=true)。

附加时机

可以在 Forge提供的Services 加载、Mixin(如 mixin 配置插件加载)或 ModMain(模组主类构造)期间发起附加。附加得越早,越有机会在目标类加载前注册好转换器。

实现思路

  1. 取得游戏进程 PID:ProcessHandle.current().pid()。
  2. 准备 agent jar:通常就是模组自身的 jar,清单中写 Agent-Class 和 Can-Retransform-Classes: true。
  3. 用当前 JVM 的 java 可执行文件(java.home 下的 bin/java)启动子进程运行下面的辅助类,参数为 PID 和 agent jar 路径;父进程等待子进程结束。
  4. 子进程通过反射依次调用 VirtualMachine.attach → loadAgent → detach。用反射是为了编译时不依赖 jdk.attach 模块。
  5. 游戏进程里,JVM 调用 agent 的 agentmain(String, Instrumentation),agent 由此拿到 Instrumentation 实例。

辅助类示例:
import java.lang.reflect.Method;
public final class ExternalAttachHelper {
    private ExternalAttachHelper() {
    }
    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            throw new IllegalArgumentException("Usage: ExternalAttachHelper <pid> <agentJar>");
        }
        String pid = args[0];
        String agentJar = args[1];
        Class<?> vmClass = Class.forName("com.sun.tools.attach.VirtualMachine");
        Method attach = vmClass.getMethod("attach", String.class);
        Object vm = attach.invoke(null, pid);
        try {
            Method loadAgent = vmClass.getMethod("loadAgent", String.class, String.class);
            loadAgent.invoke(vm, agentJar, "external-attach");
        } finally {
            Method detach = vmClass.getMethod("detach");
            detach.invoke(vm);
        }
    }
}

注意事项

  • 运行子进程的 Java 需要包含 jdk.attach 模块(完整 JDK 有,部分精简 JRE 没有)。
  • agentmain 所在类由系统类加载器加载,和模组类不在同一个类加载器里,通常要借助反射或系统类加载器可见的类,把 Instrumentation 交回模组。
  • 目标 JVM 带 -XX:+DisableAttachMechanism 时无法附加。
  • Java 21 起动态加载 agent 会打印警告(JEP 451),将来可能需要 -XX:+EnableDynamicAgentLoading 才能使用;1.20.1 所用的 Java 17 不受影响。

常用方法

  • addTransformer(ClassFileTransformer, boolean):注册转换器,类加载或重转换时收到字节码
  • retransformClasses / redefineClasses:修改已加载的类,两者的区别(基线)见各自条目;隐藏类默认不可修改
  • getAllLoadedClasses、getObjectSize
  • appendToBootstrapClassLoaderSearch、redefineModule(可给模块加 opens)

特点与限制

  • 能修改已加载的类和 java.* 等 JDK 类,弥补了 Mixin 只能处理未加载的非 JDK 类的不足。
  • 重转换 / 重定义只能改方法体,不能增删字段、方法,也不能改继承关系。
  • 字节码一般配合 objectweb asm 或 javassist 修改。
你对本条的回应 · 登录后可表态

讨论(0)

讨论不进入条目正文的版本历史,适合提出修改建议、指出问题或补充来源。
还没有人讨论这个条目。你可以提出修改建议、指出问题或补充来源。

发表话题

请先 登录 后再参与讨论。