Instrumentation:查看版本 v3
由 朝武芳乃 于 2026-10-04 02:03 保存 · 5008 字节
''本条基于 Java 17(Minecraft 1.20.1 所用版本)。''
'''Instrumentation'''(<code>java.lang.instrument.Instrumentation</code>)是 [[JavaAgent]] 修改类的标准接口,底层由 [[JVMTI]] 实现,JDK 中的具体实现类是 [[InstrumentationImpl]]。
== 获取 ==
* 启动时:<code>-javaagent:xxx.jar</code>,jar 清单声明 <code>Premain-Class</code>,JVM 调用 <code>premain(String, Instrumentation)</code>。
* 运行时附加:通过 Attach API(<code>VirtualMachine.attach(pid).loadAgent(jar)</code>),清单声明 <code>Agent-Class</code>,JVM 调用 <code>agentmain</code>。Java 9 起默认不允许附加到自身进程(需 <code>-Djdk.attach.allowAttachSelf=true</code>),常用的绕过方式见下文“运行时外部进程附加”。
* 重转换 / 重定义还需清单声明 <code>Can-Retransform-Classes</code> / <code>Can-Redefine-Classes</code>。
== 运行时外部进程附加 ==
目前最常用的做法。Java 9 起 JVM 默认不允许附加到自身进程,所以由模组另起一个 Java 子进程,让子进程附加到游戏进程并加载 agent。整个过程'''不需要任何启动参数'''(既不用 <code>-javaagent</code>,也不用 <code>-Djdk.attach.allowAttachSelf=true</code>)。
=== 附加时机 ===
可以在 [[Forge提供的Services]] 加载、[[Mixin]](如 mixin 配置插件加载)或 [[ModMain]](模组主类构造)期间发起附加。附加得越早,越有机会在目标类加载前注册好转换器。
=== 实现思路 ===
# 取得游戏进程 PID:<code>ProcessHandle.current().pid()</code>。
# 准备 agent jar:通常就是模组自身的 jar,清单中写 <code>Agent-Class</code> 和 <code>Can-Retransform-Classes: true</code>。
# 用当前 JVM 的 java 可执行文件(<code>java.home</code> 下的 <code>bin/java</code>)启动子进程运行下面的辅助类,参数为 PID 和 agent jar 路径;父进程等待子进程结束。
# 子进程通过反射依次调用 <code>VirtualMachine.attach</code> → <code>loadAgent</code> → <code>detach</code>。用反射是为了编译时不依赖 <code>jdk.attach</code> 模块。
# 游戏进程里,JVM 调用 agent 的 <code>agentmain(String, Instrumentation)</code>,agent 由此拿到 Instrumentation 实例。
辅助类示例:
<code>import java.lang.reflect.Method;</code>
<code>public final class ExternalAttachHelper {</code>
<code> private ExternalAttachHelper() {</code>
<code> }</code>
<code> public static void main(String[] args) throws Exception {</code>
<code> if (args.length < 2) {</code>
<code> throw new IllegalArgumentException("Usage: ExternalAttachHelper <pid> <agentJar>");</code>
<code> }</code>
<code> String pid = args[0];</code>
<code> String agentJar = args[1];</code>
<code> Class<?> vmClass = Class.forName("com.sun.tools.attach.VirtualMachine");</code>
<code> Method attach = vmClass.getMethod("attach", String.class);</code>
<code> Object vm = attach.invoke(null, pid);</code>
<code> try {</code>
<code> Method loadAgent = vmClass.getMethod("loadAgent", String.class, String.class);</code>
<code> loadAgent.invoke(vm, agentJar, "external-attach");</code>
<code> } finally {</code>
<code> Method detach = vmClass.getMethod("detach");</code>
<code> detach.invoke(vm);</code>
<code> }</code>
<code> }</code>
<code>}</code>
=== 注意事项 ===
* 运行子进程的 Java 需要包含 <code>jdk.attach</code> 模块(完整 JDK 有,部分精简 JRE 没有)。
* <code>agentmain</code> 所在类由系统类加载器加载,和模组类不在同一个类加载器里,通常要借助反射或系统类加载器可见的类,把 Instrumentation 交回模组。
* 目标 JVM 带 <code>-XX:+DisableAttachMechanism</code> 时无法附加。
* Java 21 起动态加载 agent 会打印警告(JEP 451),将来可能需要 <code>-XX:+EnableDynamicAgentLoading</code> 才能使用;1.20.1 所用的 Java 17 不受影响。
== 常用方法 ==
* <code>addTransformer(ClassFileTransformer, boolean)</code>:注册转换器,类加载或重转换时收到字节码
* [[retransformClasses]] / [[redefineClasses]]:修改已加载的类,两者的区别(基线)见各自条目;[[隐藏类]]默认不可修改
* <code>getAllLoadedClasses</code>、<code>getObjectSize</code>
* <code>appendToBootstrapClassLoaderSearch</code>、<code>redefineModule</code>(可给模块加 opens)
== 特点与限制 ==
* 能修改已加载的类和 <code>java.*</code> 等 JDK 类,弥补了 [[Mixin]] 只能处理未加载的非 JDK 类的不足。
* 重转换 / 重定义只能改方法体,不能增删字段、方法,也不能改继承关系。
* 字节码一般配合 [[objectweb asm]] 或 [[javassist]] 修改。