Instrumentation:查看版本 v4
由 mcst12345 于 2026-10-04 03:02 保存 · 8061 字节
'''Instrumentation'''(<code>java.lang.instrument.Instrumentation</code>)是jvmtiEnv在Java層的封裝,默認實現為 [[InstrumentationImpl]]。
== 获取 ==
* 启动时:<code>-javaagent:xxx.jar</code>,JVM將調用Premain-Class指定的agent類的premain(String,Instrumentation)方法。
* 运行时附加:通过 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>),常用的绕过方式见下文“运行时外部进程附加”。Attach API底層調用Agent_OnAttach函數(位於instrument動態庫內),能夠調用native函數的環境下也可直接調用Agent_OnAttach實現附加agent獲取Instrumentation。
* 重转换 / 重定义还需清单声明 <code>Can-Retransform-Classes</code> / <code>Can-Redefine-Classes</code>。
== 运行时外部进程附加 ==
目前最常用的做法。Java 9 起 JVM 默认不允许附加到自身进程,所以由模组另起一个 Java 子进程,让子进程附加到游戏进程并加载 agent。整个过程'''不需要任何启动参数'''(既不用 <code>-javaagent</code>,也不用 <code>-Djdk.attach.allowAttachSelf=true</code>)。
=== 附加时机 ===
javaagent可在任意時間附加,但已加載的類不會接收到ClassFileLoadHook事件,除非手動觸發redefine/retransform。
=== 实现思路 ===
# 取得游戏进程 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]] 修改。
'''Instrumentation'''(<code>java.lang.instrument.Instrumentation</code>)is the Java wrapper of jvmtiEnv,the default implementation is [[InstrumentationImpl]].
== Obtain ==
* through commandline agent/static: use -javaagent:/path/to/your/agent.jar to load a javaagent statically. JVM will call the premain(String,Instrumentation) method declared in the class specified by Premain-Class attribute in your agent jar.
* runtime attach/dynamic: There are various ways to attach an javaagent at runtime, however they all delegate to Agent_OnAttach function in instrument library. So that if you can invoke c functions the most convenient way to load a javaagent dynamically is to invoke Agent_OnAttach function. Or that if you are using Java9+ you can just invoke sun.instrument.InstrumentationImpl.loadAgent0(String).
== Commonly used methods ==
* <code>addTransformer(ClassFileTransformer, boolean)</code> Add a ClassFileTransformer that can receive and transform a class when JVM attempts to load it. The boolean argument specifies whether the transformer will receive the event when it's retransformation.
* <code>removeTransformer(ClassFileTransformer)</code> Remove a ClassFileTransformer. You should perform this operation on the same Instrumentation that you registered this transformer.
* <code>retransformClasses(Class<?>... classes)</code> retransform all the classes in the classes array. JVM will reconstruct the classfile using runtime data if no cache is available and then feed them into all present ClassFileTransformer that is registered as retransformable, in the end redefine all the classes with their new classfile. Note that if any redefination fails, all the classes will remain untouched.
* <code>redefineClasses(ClassDefinition... definitions)</code> Redefine all the classes that is required by the definitions array. ClassDefinition contains a java.lang.Class field and a byte[] field, the Class field declares which class to be redefined, the byte[] field declares its new defination.
* <code>getAllLoadedClasses()</code> get all loaded classes in current JVM.
* <code>getInitiatedClasses(ClassLoader loader)</code> get all initiated classes loaded by specific classloader
* <code>getObjectSize(Object objectToSize)</code> Get the size of a Java object. Note that the "size" here is how much memory the object consumes, not essentially the actual size of the object. (It may contain padding)
* <code>appendToBootstrapClassLoaderSearch(JarFile jarfile)</code> Append jar file to bootstrap classloader.
* <code>appendToSystemClassLoaderSearch(JarFile jarfile)</code> Append jar file to system classloader.
* <code>setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)</code> Set a [[native method prefix]] corresponding to a ClassFileTransformer
* <code>redefineModule(Module module,Set<Module> extraReads,Map<String, Set<Module>> extraExports,Map<String, Set<Module>> extraOpens,Set<Class<?>> extraUses,Map<Class<?>, List<Class<?>>> extraProvides)</code> *Java9+ Redefines a module. You can only expand its reads, uses, opens, provides.