概述
Package java.lang.instrument
: 提供允许Java编程语言代理检测JVM上运行的程序的服务。检测机制是修改方法的字节码。
其中包含两个接口:
ClassFileTransformer
Instrumentation
一个类:
ClassDefinition
以及几个异常类:
IllegalClassFormatException
UnmodifiableClassException
使用方式
命令行启动:
java -javaagent:jarpath[=选项]
jarpath是代理JAR文件的路径。options是代理选项。代理JAR文件必须符合JAR文件规范。
代理JAR文件的清单必须包含属性Premain-Class
。此属性的值是代理类的名称。代理类必须实现一个premain
类似的公共静态方法(类似于main)。在JVM初始化后,premain将按照指定代理的顺序调用每个方法,然后main将调用真正的应用程序方法。每个premain方法都必须返回,以便启动序列继续进行。
JVM实现尝试在代理类上调用:
public static void premain(String agentArgs, Instrumentation inst);
如果代理类没有实现改方法,在JVM尝试调用:
public static void premain(String agentArgs);
代理类可能还有一个agentmain方法,可以在VM启动后启动代理时使用。使用命令行选项启动代理时,agentmain不会调用该方法。
代理类将由系统类加载器加载。
每个代理都通过agentArgs参数传递其代理选项。代理选项作为单个字符串传递,任何额外的解析都应该由代理本身执行。
如果无法解析代理,JVM将终止。如果permain方法抛出未捕获的异常,JVM将中止。
VM启动后启动代理
一个实现可以提供一种机制来在 VM 启动后的某个时间启动代理。关于如何启动的细节是特定于实现的,但通常应用程序已经启动并且它的 main方法已经被调用。如果实现支持在 VM 启动后启动代理,则以下适用:
- 代理 JAR 的清单必须包含属性Agent-Class。此属性的值是代理类的名称。
- 代理类必须实现公共静态agentmain方法。
- 系统类加载器 ( ClassLoader.getSystemClassLoader) 必须支持将代理 JAR 文件添加到系统类路径的机制。
代理 JAR 附加到系统类路径。这是通常加载包含应用程序main方法的类的类加载器。加载代理类,JVM 尝试调用该agentmain方法。JVM 首先尝试在代理类上调用以下方法:
public static void agentmain(String agentArgs, Instrumentation inst);
如果代理类未实现此方法,则 JVM 将尝试调用:
public static void agentmain(String agentArgs);
premain当使用命令行选项启动代理时, 代理类也可能有一个使用方法。在 VM 启动后启动代理时,premain 不会调用该方法。
代理通过agentArgs参数传递其代理选项。代理选项作为单个字符串传递,任何额外的解析都应该由代理本身执行。
该agentmain方法应执行启动代理所需的任何必要初始化。启动完成后,该方法应返回。如果代理无法启动(例如,代理类无法加载,或者代理类没有符合的agentmain方法),JVM 不会中止。如果该agentmain方法抛出未捕获的异常,它将被忽略。
清单属性
为代理 JAR 文件定义了以下清单属性:
- Premain-Class: 在 JVM 启动时指定代理时,此属性指定代理类。也就是说,包含premain方法的类。在 JVM 启动时指定代理时,此属性是必需的。如果该属性不存在,JVM 将中止。注意:这是一个类名,而不是文件名或路径。
- Agent-Class: 如果实现支持在 VM 启动后某个时间启动代理的机制,则该属性指定代理类。也就是说,包含agentmain方法的类。此属性是必需的,如果它不存在,代理将不会启动。注意:这是一个类名,而不是文件名或路径。
- Boot-Class-Path: 引导类加载器要搜索的路径列表。路径代表目录或库(在许多平台上通常称为 JAR 或 zip 库)。在定位类的平台特定机制失败后,引导类加载器会搜索这些路径。按照列出的顺序搜索路径。列表中的路径由一个或多个空格分隔。路径采用分层 URI 的路径组件的语法。如果路径以斜杠字符 (’/’) 开头,则该路径是绝对路径,否则是相对的。根据代理 JAR 文件的绝对路径解析相对路径。格式错误和不存在的路径将被忽略。在 VM 启动后某个时间启动代理时,不代表 JAR 文件的路径将被忽略。该属性是可选的。在 VM 启动后某个时间启动代理时,不代表 JAR 文件的路径将被忽略。该属性是可选的。在 VM 启动后某个时间启动代理时,不代表 JAR 文件的路径将被忽略。该属性是可选的。
- Can-Redefine-Classes: 布尔值(true或false,大小写无关)。是否能够重新定义此代理所需的类。其他值true被考虑false。此属性是可选的,默认为false。
- Can-Retransform-Classes: 布尔值(true或false,大小写无关)。是否具有重新转换此代理所需的类的能力。其他值true被考虑false。此属性是可选的,默认为false。
- Can-Set-Native-Method-Prefix: 布尔值(true或false,大小写无关)。是否能够设置此代理所需的本机方法前缀。其他值true被考虑false。此属性是可选的,默认为false。
代理 JAR 文件 的清单中可能同时包含Premain-Class和Agent-Class属性。当使用该-javaagent选项在命令行上启动代理时,该Premain-Class属性指定代理类的名称,而该Agent-Class属性将被忽略。类似地,如果代理在 VM 启动后的某个时间启动,则该Agent-Class属性指定代理类的名称(Premain-Class属性的值被忽略)。
ClassFileTransformer
代理提供此接口的实现以转换类文件。转换发生在 JVM 定义类之前。
主要方法
byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException
该方法的实现可能转化提供的类文件,返回一个新的替代文件。
有两种transformers, 由Instrumentation.addTransformer(ClassFileTransformer transformer, boolean canRetransform) 中的参数canRetransform决定:
- canRetransform为true,为retransformation capable。代表可再次转化。
- canRetransform为false,为retransformation incapable。代表不可以再次转化。
当转化器被addTransformer
注册后,每次新类定义和重定义,该转化器都将被调用。
对新类的请求定义由ClassLoader.defineClass
完成。
转化器在处理请求过程中,在类验证和应用前被调用。
多个转化器之间链式调用,也就是说前一个转化器的结果是后一个转化器的输入。
转化器按以下的顺序调用:
- Retransformation incapable transformers
- Retransformation incapable native transformers
- Retransformation capable transformers
- Retransformation capable native transformers
对于重新转化,the retransformation incapable transformers
不会被调用,而是由前面转化的结果替代。
本地转化器由JVMT 接口的ClassFileLoadHook
事件提供。
对于传入转化器的字节数组classfileBuffer
参数:
- 对于新的类,传递给ClassLoader.defineClass
- 对于类重新定义, definitions.getDefinitionClassFile()其中 definitions的参数是 Instrumentation.redefineClasses
- 对于类重新转换,传递给新类定义的字节,或者,如果重新定义,最后一次重新定义的字节,由无法重新转换的转换器自动重新应用且未更改的所有转换;详情见 Instrumentation.retransformClasses
如果实现方法确定不需要转换,它应该返回null。否则,它应该创建一个新byte[]数组,将输入classfileBuffer以及所有所需的转换复制到其中,然后返回新数组。classfileBuffer不得修改输入。
在重新转换和重新定义的情况下,转换器必须支持重定义语义:如果转换器在初始定义期间更改的类后来被重新转换或重新定义,转换器必须确保第二个类输出类文件是第一个输出的合法重定义类文件。
如果转换器抛出异常(它没有捕获),后续转换器仍将被调用,并且仍将尝试加载、重新定义或重新转换。因此,抛出异常与返回具有相同的效果null。为了防止在转换器代码中生成未经检查的异常时出现意外行为,转换器可以捕获Throwable. 如果转换器认为classFileBuffer不代表格式正确的类文件,它应该抛出一个IllegalClassFormatException; 虽然这与返回 null 具有相同的效果。它有助于记录或调试格式损坏。
Instrument
此类提供检测 Java 编程语言代码所需的服务。检测是将字节码添加到方法中,以收集工具使用的数据。由于更改纯粹是附加的,因此这些工具不会修改应用程序状态或行为。此类良性工具的示例包括监控代理、分析器、覆盖分析器和事件记录器。
有两种方法可以获取Instrumentation接口的实例 :
- 当 JVM 以指示代理类的方式启动时。在这种情况下,一个Instrumentation实例被传递给premain代理类的方法。
- 当 JVM 提供在 JVM 启动后某个时间启动代理的机制时。在这种情况下,一个Instrumentation 实例被传递给agentmain代理代码的方法。
这些机制在包规范中进行了描述 。
一旦代理获取了一个Instrumentation实例,代理就可以随时调用实例上的方法。
主要方法
- addTransformer 注册转化器
- appendToBootStrapClassLoaderSearch 指定一个JAR文件,其中包括要有引导程序类加载器定义的检测类。
- appendToSystemClassLoaderSearch 指定一个JAR文件,其中包括系统类加载器定义的检测类。
- getAllLoadedClasses 返回JVM当前加载的所有类的数组。
- getInitiatedClasses 返回一个所有类的数组,参数loader是一个启动加载器。
- getObjectSize 返回指定对象消耗的存储量的特定于实现的近似值。
- isModifiableClass 确定类是否可以通过重新转换或者重新定义进行修改。
- isNativeMethodPrefixSupported 返回当前 JVM 配置是否支持 设置本机方法前缀。
- isRedefineClassesSupported 返回当前JVM配置是否支持重新定义类。
- isRetransformClassesSupported 返回当前 JVM 配置是否支持类的重新转换。
- redefineClasses 使用提供的类文件重新定义提供的类集。
- removeTransformer 取消注册提供的转化器。
- retransformClasses 重新转换提供的类集。
- setNativeMethodPrefix 此方法通过允许重试应用到名称的前缀来修改本机方法解析的失败处理。
参考文档
https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html