Instrumentation:版本对比 v1 → v2
v1(2026-10-04 01:17,朝武芳乃)→ v2(2026-10-04 01:28,朝武芳乃)
共 53 行变更 · 新增 44 行 · 删除 1 行(左右分栏:左为旧版本,右为新版本)
| ⋯ 省略 4 行未变内容 ⋯ | |||
| 5 | == 获取 == | 5 | == 获取 == |
| 6 | * 启动时:<code>-javaagent:xxx.jar</code>,jar 清单声明 <code>Premain-Class</code>,JVM 调用 <code>premain(String, Instrumentation)</code>。 | 6 | * 启动时:<code>-javaagent:xxx.jar</code>,jar 清单声明 <code>Premain-Class</code>,JVM 调用 <code>premain(String, Instrumentation)</code>。 |
| 7 | * 运行时附加:通过 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>)。 | ||
| 7 | * 运行时附加:通过 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>),常用的绕过方式见下文“运行时外部进程附加”。 | ||
| 8 | * 重转换 / 重定义还需清单声明 <code>Can-Retransform-Classes</code> / <code>Can-Redefine-Classes</code>。 | 8 | * 重转换 / 重定义还需清单声明 <code>Can-Retransform-Classes</code> / <code>Can-Redefine-Classes</code>。 |
| 9 | 9 | ||
| 10 | == 运行时外部进程附加 == | ||
| 11 | 目前最常用的做法。Java 9 起 JVM 默认不允许附加到自身进程,所以由模组另起一个 Java 子进程,让子进程附加到游戏进程并加载 agent。整个过程'''不需要任何启动参数'''(既不用 <code>-javaagent</code>,也不用 <code>-Djdk.attach.allowAttachSelf=true</code>)。 | ||
| 12 | |||
| 13 | === 附加时机 === | ||
| 14 | 可以在 [[Forge提供的Services]] 加载、[[Mixin]](如 mixin 配置插件加载)或 [[ModMain]](模组主类构造)期间发起附加。附加得越早,越有机会在目标类加载前注册好转换器。 | ||
| 15 | |||
| 16 | === 实现思路 === | ||
| 17 | # 取得游戏进程 PID:<code>ProcessHandle.current().pid()</code>。 | ||
| 18 | # 准备 agent jar:通常就是模组自身的 jar,清单中写 <code>Agent-Class</code> 和 <code>Can-Retransform-Classes: true</code>。 | ||
| 19 | # 用当前 JVM 的 java 可执行文件(<code>java.home</code> 下的 <code>bin/java</code>)启动子进程运行下面的辅助类,参数为 PID 和 agent jar 路径;父进程等待子进程结束。 | ||
| 20 | # 子进程通过反射依次调用 <code>VirtualMachine.attach</code> → <code>loadAgent</code> → <code>detach</code>。用反射是为了编译时不依赖 <code>jdk.attach</code> 模块。 | ||
| 21 | # 游戏进程里,JVM 调用 agent 的 <code>agentmain(String, Instrumentation)</code>,agent 由此拿到 Instrumentation 实例。 | ||
| 22 | |||
| 23 | 辅助类示例: | ||
| 24 | <code>import java.lang.reflect.Method;</code> | ||
| 25 | <code>public final class ExternalAttachHelper {</code> | ||
| 26 | <code> private ExternalAttachHelper() {</code> | ||
| 27 | <code> }</code> | ||
| 28 | <code> public static void main(String[] args) throws Exception {</code> | ||
| 29 | <code> if (args.length < 2) {</code> | ||
| 30 | <code> throw new IllegalArgumentException("Usage: ExternalAttachHelper <pid> <agentJar>");</code> | ||
| 31 | <code> }</code> | ||
| 32 | <code> String pid = args[0];</code> | ||
| 33 | <code> String agentJar = args[1];</code> | ||
| 34 | <code> Class<?> vmClass = Class.forName("com.sun.tools.attach.VirtualMachine");</code> | ||
| 35 | <code> Method attach = vmClass.getMethod("attach", String.class);</code> | ||
| 36 | <code> Object vm = attach.invoke(null, pid);</code> | ||
| 37 | <code> try {</code> | ||
| 38 | <code> Method loadAgent = vmClass.getMethod("loadAgent", String.class, String.class);</code> | ||
| 39 | <code> loadAgent.invoke(vm, agentJar, "external-attach");</code> | ||
| 40 | <code> } finally {</code> | ||
| 41 | <code> Method detach = vmClass.getMethod("detach");</code> | ||
| 42 | <code> detach.invoke(vm);</code> | ||
| 43 | <code> }</code> | ||
| 44 | <code> }</code> | ||
| 45 | <code>}</code> | ||
| 46 | |||
| 47 | === 注意事项 === | ||
| 48 | * 运行子进程的 Java 需要包含 <code>jdk.attach</code> 模块(完整 JDK 有,部分精简 JRE 没有)。 | ||
| 49 | * <code>agentmain</code> 所在类由系统类加载器加载,和模组类不在同一个类加载器里,通常要借助反射或系统类加载器可见的类,把 Instrumentation 交回模组。 | ||
| 50 | * 目标 JVM 带 <code>-XX:+DisableAttachMechanism</code> 时无法附加。 | ||
| 51 | * Java 21 起动态加载 agent 会打印警告(JEP 451),将来可能需要 <code>-XX:+EnableDynamicAgentLoading</code> 才能使用;1.20.1 所用的 Java 17 不受影响。 | ||
| 52 | |||
| 10 | == 常用方法 == | 53 | == 常用方法 == |
| 11 | * <code>addTransformer(ClassFileTransformer, boolean)</code>:注册转换器,类加载或重转换时收到字节码 | 54 | * <code>addTransformer(ClassFileTransformer, boolean)</code>:注册转换器,类加载或重转换时收到字节码 |
| ⋯ 省略 8 行未变内容 ⋯ | |||