有两种方式获取Instrumentation接口的实例:
当 JVM 以指示代理类的方式启动时。在这种情况下,
Instrumentation实例将传递给代理类的premain方法。当 JVM 提供一种机制以在 JVM 启动后的某个时间启动代理时。在这种情况下,
Instrumentation实例将传递给代理代码的agentmain方法。
这些机制在 包装规格 中进行了描述。
一旦代理获取了Instrumentation实例,代理就可以随时调用实例上的方法。
- API 注意:
- 该接口不打算在 java.instrument 模块之外实现。
- 自从:
- 1.5
-
方法总结
修饰符和类型方法描述voidaddTransformer(ClassFileTransformer transformer) 注册提供的转换器。voidaddTransformer(ClassFileTransformer transformer, boolean canRetransform) 注册提供的转换器。void指定一个 JAR 文件,其中包含要由引导类加载器定义的检测类。voidappendToSystemClassLoaderSearch(JarFile jarfile) 指定一个 JAR 文件,其中包含要由系统类加载器定义的检测类。Class[]返回 JVM 当前加载的所有类的数组。Class[]getInitiatedClasses(ClassLoader loader) longgetObjectSize(Object objectToSize) 返回指定对象消耗的存储量的特定于实现的近似值。booleanisModifiableClass(Class<?> theClass) booleanisModifiableModule(Module module) 测试是否可以使用redefineModule修改模块。boolean返回当前 JVM 配置是否支持 设置本地方法前缀 。boolean返回当前 JVM 配置是否支持类的重新定义。boolean返回当前 JVM 配置是否支持类的重新转换。voidredefineClasses(ClassDefinition... definitions) 使用提供的类文件重新定义提供的类集。voidredefineModule(Module module, Set<Module> extraReads, Map<String, Set<Module>> extraExports, Map<String, Set<Module>> extraOpens, Set<Class<?>> extraUses, Map<Class<?>, List<Class<?>>> extraProvides) 重新定义一个模块以扩展它读取的模块集、它导出或打开的包集或它使用或提供的服务。booleanremoveTransformer(ClassFileTransformer transformer) 注销提供的转换器。voidretransformClasses(Class<?>... classes) 重新转换提供的类集。voidsetNativeMethodPrefix(ClassFileTransformer transformer, String prefix) 此方法通过允许使用应用于名称的前缀重试来修改本机方法解析的失败处理。
-
方法详情
-
addTransformer
注册提供的转换器。所有未来的类定义都将被转换器看到,除了任何已注册的转换器所依赖的类的定义。转换器在加载类时被调用,当它们是 redefined 时。如果canRetransform为真,则当它们为 重新改造 时。ClassFileTransformer定义转换调用的顺序。如果一个转换器在执行过程中抛出异常,JVM 仍然会依次调用其他已注册的转换器。可以多次添加同一个转换器,但强烈建议不要这样做——通过创建转换器类的新实例来避免这种情况。此方法旨在用于检测,如 类规范 中所述。
- 参数:
transformer- 要注册的转换器canRetransform- 这个转换器的转换是否可以重新转换- 抛出:
NullPointerException- 如果通过了null转换器UnsupportedOperationException- 如果canRetransform为真且 JVM 的当前配置不允许重新转换(isRetransformClassesSupported()为假)- 自从:
- 1.6
-
addTransformer
注册提供的转换器。与
addTransformer(transformer, false)相同。- 参数:
transformer- 要注册的转换器- 抛出:
NullPointerException- 如果通过了null转换器- 参见:
-
removeTransformer
注销提供的转换器。未来的类定义将不会显示给转换器。删除最近添加的匹配转换器实例。由于类加载的多线程特性,转换器有可能在移除后接收调用。应该防御性地编写变形金刚以预料到这种情况。- 参数:
transformer- 要注销的转换器- 返回:
- 如果找到并移除转换器,则为 true;如果未找到转换器,则为 false
- 抛出:
NullPointerException- 如果通过了null转换器
-
isRetransformClassesSupported
boolean isRetransformClassesSupported()返回当前 JVM 配置是否支持类的重新转换。重新转换已加载类的能力是 JVM 的一项可选功能。仅当代理 JAR 文件中的Can-Retransform-Classes清单属性设置为true(如 包装规格 中所述)且 JVM 支持此功能时,才支持重新转换。在单个 JVM 的单个实例化期间,多次调用此方法将始终返回相同的答案。- 返回:
- 如果当前 JVM 配置支持类的重新转换,则为 true,否则为 false。
- 自从:
- 1.6
- 参见:
-
retransformClasses
重新转换提供的类集。此功能有助于对已加载的类进行检测。当最初加载类或它们是 redefined 时,可以使用
ClassFileTransformer转换初始类文件字节。此函数重新运行转换过程(无论之前是否发生过转换)。此重新转换遵循以下步骤:- 从初始类文件字节开始
- 对于每个添加了
canRetransformfalse 的转换器,transform在最后一个类加载或重新定义期间返回的字节被重新用作转换的输出;请注意,这相当于重新应用之前的转换,没有改变;除了不调用transform方法。 - 对于添加了
canRetransformtrue 的每个转换器,在这些转换器中调用了transform方法 - 转换后的类文件字节作为类的新定义安装
ClassFileTransformer中描述了转换顺序。同样的顺序用于自动重新应用不能重新转换的转换。初始类文件字节表示传递给
ClassLoader.defineClass或redefineClasses的字节(在应用任何转换之前),但是它们可能不完全匹配。常量池可能没有相同的布局或内容。常量池可能有更多或更少的条目。常量池条目可能有不同的顺序;但是,方法字节码中的常量池索引将对应。某些属性可能不存在。如果顺序没有意义,例如方法的顺序,则可能不会保留顺序。此方法对一个集合进行操作,以允许同时对多个类进行相互依赖的更改(A 类的重新转换可能需要 B 类的重新转换)。
如果重新转换的方法具有活动堆栈帧,则这些活动堆栈帧将继续运行原始方法的字节码。重新转换的方法将用于新的调用。
此方法不会导致任何初始化,但在通常的 JVM 语义下会发生的初始化除外。换句话说,重新定义一个类不会导致其初始化程序运行。静态变量的值将保持在调用之前的状态。
重新转换的类的实例不受影响。
JVM TI 重新转换类 中描述了支持的类文件更改。在应用转换之前,不会检查、验证和安装类文件字节,如果生成的字节有误,此方法将抛出异常。
如果此方法抛出异常,则没有类被重新转换。
此方法旨在用于检测,如 类规范 中所述。
- 参数:
classes- 要重新转换的类数组;允许零长度数组,在这种情况下,此方法不执行任何操作- 抛出:
UnmodifiableClassException- 如果无法修改指定的类(isModifiableClass(java.lang.Class<?>)将返回false)UnsupportedOperationException- 如果 JVM 的当前配置不允许重新转换(isRetransformClassesSupported()为 false)或重新转换试图进行不受支持的更改ClassFormatError- 如果数据不包含有效类NoClassDefFoundError- 如果类文件中的名称不等于类的名称UnsupportedClassVersionError- 如果不支持类文件版本号ClassCircularityError- 如果新类包含循环LinkageError- 如果发生链接错误NullPointerException- 如果提供的类数组或其任何组件是null。- 自从:
- 1.6
- 参见:
-
isRedefineClassesSupported
boolean isRedefineClassesSupported()返回当前 JVM 配置是否支持类的重新定义。重新定义已加载类的能力是 JVM 的一项可选功能。仅当代理 JAR 文件中的Can-Redefine-Classes清单属性设置为true(如 包装规格 中所述)且 JVM 支持此功能时,才支持重新定义。在单个 JVM 的单个实例化期间,多次调用此方法将始终返回相同的答案。- 返回:
- 如果当前 JVM 配置支持类的重新定义,则为 true,否则为 false。
- 参见:
-
redefineClasses
void redefineClasses(ClassDefinition ... definitions) throws ClassNotFoundException , UnmodifiableClassException 使用提供的类文件重新定义提供的类集。此方法用于在不引用现有类文件字节的情况下替换类的定义,就像从源代码重新编译以进行修复并继续调试时可能会做的那样。在要转换现有类文件字节的地方(例如在字节码检测中)应该使用
retransformClasses。此方法对集合进行操作,以允许同时对多个类进行相互依赖的更改(类 A 的重新定义可能需要重新定义类 B)。
如果重新定义的方法具有活动堆栈帧,则这些活动堆栈帧将继续运行原始方法的字节码。重新定义的方法将用于新的调用。
此方法不会导致任何初始化,但在通常的 JVM 语义下会发生的初始化除外。换句话说,重新定义一个类不会导致其初始化程序运行。静态变量的值将保持在调用之前的状态。
重新定义的类的实例不受影响。
JVM TI 重新定义类 中描述了支持的类文件更改。在应用转换之前,不会检查、验证和安装类文件字节,如果生成的字节有误,此方法将抛出异常。
如果此方法抛出异常,则没有类被重新定义。
此方法旨在用于检测,如 类规范 中所述。
- 参数:
definitions- 要使用相应定义重新定义的类数组;允许零长度数组,在这种情况下,此方法不执行任何操作- 抛出:
UnmodifiableClassException- 如果无法修改指定的类(isModifiableClass(java.lang.Class<?>)将返回false)UnsupportedOperationException- 如果 JVM 的当前配置不允许重新定义(isRedefineClassesSupported()为 false)或重新定义试图进行不受支持的更改ClassFormatError- 如果数据不包含有效类NoClassDefFoundError- 如果类文件中的名称不等于类的名称UnsupportedClassVersionError- 如果不支持类文件版本号ClassCircularityError- 如果新类包含循环LinkageError- 如果发生链接错误NullPointerException- 如果提供的定义数组或其任何组件是nullClassNotFoundException- 永远不会被抛出(仅出于兼容性原因存在)- 参见:
-
isModifiableClass
测试类是否可由 再改造 或 重新定义 修改。如果类是可修改的,则此方法返回true。如果类不可修改,则此方法返回false。对于要重新转换的类,
isRetransformClassesSupported()也必须为真。但isRetransformClassesSupported()的值不影响此函数返回的值。对于要重新定义的类,isRedefineClassesSupported()也必须为真。但isRedefineClassesSupported()的值不影响此函数返回的值。原始类(例如
java.lang.Integer.TYPE)和数组类永远不可修改。- 参数:
theClass- 检查是否可修改的类- 返回:
- 参数类是否可修改
- 抛出:
NullPointerException- 如果指定的类是null。- 自从:
- 1.6
- 参见:
-
getAllLoadedClasses
Class [] getAllLoadedClasses()返回 JVM 当前加载的所有类的数组。返回的数组包括所有类和接口,包括 隐藏类或接口 和所有类型的数组类。- 返回:
- 包含 JVM 加载的所有类的数组,如果没有则为零长度
-
getInitiatedClasses
返回所有类的数组,loader可以通过ClassLoader::loadClass、Class::forName和字节码链接按名称找到这些类。也就是说,loader已被记录为初始加载程序的所有类。如果提供的loader是null,则返回引导类加载器可以按名称找到的类。- 参数:
loader- 将返回其启动类列表的加载器- 返回:
-
包含
loader可以通过名称找到的所有类的数组;如果没有则为零长度
-
getObjectSize
返回指定对象消耗的存储量的特定于实现的近似值。结果可能包括对象的部分或全部开销,因此对于实现内的比较很有用,但对实现之间的比较没有用。估计值可能会在 JVM 的单次调用期间发生变化。- 参数:
objectToSize- 要调整大小的对象- 返回:
- 指定对象消耗的存储量的特定于实现的近似值
- 抛出:
NullPointerException- 如果提供的对象是null。
-
appendToBootstrapClassLoaderSearch
指定一个 JAR 文件,其中包含要由引导类加载器定义的检测类。当虚拟机的内置类加载器(称为“引导类加载器”)搜索类失败时,也会搜索
JAR file中的条目。可以多次使用此方法以添加多个 JAR 文件以按照调用此方法的顺序进行搜索。
代理应该注意确保 JAR 不包含任何类或资源,除了那些由引导类加载器为检测目的定义的类或资源。不遵守此警告可能会导致难以诊断的意外行为。例如,假设有一个加载器 L,L 的委托父类是引导类加载器。此外,类 C(由 L 定义的类)中的方法引用了非公共访问器类 C$1。如果 JAR 文件包含类 C$1,那么委托给引导类加载器将导致 C$1 由引导类加载器定义。在此示例中,将抛出一个
IllegalAccessError,这可能会导致应用程序失败。避免此类问题的一种方法是为检测类使用唯一的包名称。Java 虚拟机规范指定后续尝试解析 Java 虚拟机先前未成功尝试解析的符号引用总是失败,并抛出与初始解析尝试相同的错误。因此,如果 JAR 文件包含对应于 Java 虚拟机未能成功解析引用的类的条目,则后续解析该引用的尝试将失败,并出现与初始尝试相同的错误。
- 参数:
jarfile- 引导类加载器搜索类失败时要搜索的 JAR 文件。- 抛出:
NullPointerException- 如果jarfile是null。- 自从:
- 1.6
- 参见:
-
appendToSystemClassLoaderSearch
指定一个 JAR 文件,其中包含要由系统类加载器定义的检测类。当用于委托的系统类加载器(参见getSystemClassLoader())搜索类失败时,也会搜索JarFile中的条目。可以多次使用此方法以添加多个 JAR 文件以按照调用此方法的顺序进行搜索。
代理应注意确保 JAR 不包含除系统类加载器为检测目的而定义的类或资源之外的任何类或资源。不遵守此警告可能会导致难以诊断的意外行为(请参阅
appendToBootstrapClassLoaderSearch)。如果系统类加载器实现了一个名为
appendToClassPathForInstrumentation的方法,该方法接受一个类型为java.lang.String的参数,则系统类加载器支持添加要搜索的 JAR 文件。该方法不需要具有public访问权限。 JAR 文件的名称是通过调用jarfile上的getName()方法获得的,并将其作为参数提供给appendToClassPathForInstrumentation方法。Java 虚拟机规范指定后续尝试解析 Java 虚拟机先前未成功尝试解析的符号引用总是失败,并抛出与初始解析尝试相同的错误。因此,如果 JAR 文件包含对应于 Java 虚拟机未能成功解析引用的类的条目,则后续解析该引用的尝试将失败,并出现与初始尝试相同的错误。
此方法不会更改
java.class.pathsystem property的值。- 参数:
jarfile- 当系统类加载器搜索类失败时要搜索的 JAR 文件。- 抛出:
UnsupportedOperationException- 如果系统类加载器不支持附加要搜索的 JAR 文件。NullPointerException- 如果jarfile是null。- 自从:
- 1.6
- 参见:
-
isNativeMethodPrefixSupported
boolean isNativeMethodPrefixSupported()返回当前 JVM 配置是否支持 设置本地方法前缀 。设置本机方法前缀的能力是 JVM 的一项可选功能。仅当代理 JAR 文件中的Can-Set-Native-Method-Prefix清单属性设置为true(如 包装规格 中所述)并且 JVM 支持此功能时,才支持设置本机方法前缀。在单个 JVM 的单个实例化期间,多次调用此方法将始终返回相同的答案。- 返回:
- 如果当前 JVM 配置支持设置本地方法前缀,则为 true,否则为 false。
- 自从:
- 1.6
- 参见:
-
setNativeMethodPrefix
此方法通过允许使用应用于名称的前缀重试来修改本机方法解析的失败处理。当与ClassFileTransformer一起使用时,它可以检测本机方法。由于不能直接检测本地方法(它们没有字节码),因此必须用可以检测的非本地方法包装它们。例如,如果我们有:
native boolean foo(int x);
我们可以转换类文件(在类的初始定义期间使用 ClassFileTransformer),使其变为:
boolean foo(int x) { ... record entry to foo ... return wrapped_foo(x); } native boolean wrapped_foo(int x);其中
foo成为带有附加前缀“wrapped_”的实际本地方法的包装器。请注意,“wrapped_”是一个糟糕的前缀选择,因为它可能构成现有方法的名称,因此类似“$$$MyAgentWrapped$$$_”的名称会更好,但会使这些示例的可读性降低。包装器将允许在本地方法调用时收集数据,但现在问题变成了将包装方法与本地实现链接起来。也就是说,方法
wrapped_foo需要解析为foo的本机实现,它可能是:Java_somePackage_someClass_foo(JNIEnv* env, jint x)
此函数允许指定前缀并进行正确的解析。具体来说,当标准解析失败时,将考虑前缀重试解析。解析有两种方式,使用 JNI 函数
RegisterNatives的显式解析和正常的自动解析。对于RegisterNatives,JVM 将尝试此关联:method(foo) -> nativeImplementation(foo)如果失败,将在方法名称前加上指定的前缀重试解析,从而产生正确的解析:
method(wrapped_foo) -> nativeImplementation(foo)对于自动解析,JVM 将尝试:
method(wrapped_foo) -> nativeImplementation(wrapped_foo)如果失败,将使用从实现名称中删除的指定前缀重试解析,从而产生正确的解析:
method(wrapped_foo) -> nativeImplementation(foo)请注意,由于前缀仅在标准解析失败时使用,因此可以选择性地包装本地方法。
由于每个
ClassFileTransformer都可以对字节码进行自己的转换,因此可以应用不止一层的包装器。因此每个转换器都需要自己的前缀。由于转换是按顺序应用的,因此前缀(如果应用)将以相同的顺序应用(参见addTransformer)。因此,如果三个转换器应用包装器,foo可能会变成$trans3_$trans2_$trans1_foo。但是,比方说,如果第二个转换器没有对foo应用包装器,它就只是$trans3_$trans1_foo。为了能够有效地确定前缀序列,只有在存在非本地包装器时才应用中间前缀。因此,在最后一个示例中,即使$trans1_foo不是本机方法,也会应用$trans1_前缀,因为$trans1_foo存在。- 参数:
transformer- 使用此前缀包装的 ClassFileTransformer。prefix- 重试失败的本机方法解析时应用于包装的本机方法的前缀。如果 prefix 是null或空字符串,则不会为此转换器重试失败的本机方法解析。- 抛出:
NullPointerException- 如果通过了null转换器。UnsupportedOperationException- 如果 JVM 的当前配置不允许设置本地方法前缀(isNativeMethodPrefixSupported()为 false)。IllegalArgumentException- 如果转换器未注册(参见addTransformer)。- 自从:
- 1.6
-
redefineModule
void redefineModule(Module module, Set <Module > extraReads, Map <String , Set <Module >> extraExports, Map <String , Set <Module >> extraOpens, Set <Class <?>> extraUses, Map <Class <?>, List <Class <?>>> extraProvides) 重新定义一个模块以扩展它读取的模块集、它导出或打开的包集或它使用或提供的服务。此方法有助于在命名模块中检测代码,其中检测需要更改读取的模块集、导出或打开的包或使用或提供的服务。这种方法不能减少一个模块读取的模块集合,也不能减少它导出或打开的包集合,也不能减少它使用或提供的服务集合。当调用此方法来重新定义未命名模块时,此方法是空操作。
使用服务类型的每个检测站点都可以访问服务类型。此方法不检查服务类型是否是模块的成员,或者是否在由它读取的另一个模块导出到模块的包中。
extraExports参数是要导出的附加包的映射。extraOpens参数是要打开的附加包的映射。在这两种情况下,映射键都是包的完全限定名称,如第 6.5.3 节中所定义Java 语言规范,例如"java.lang"。映射值是包应该导出或打开的非空模块集。extraProvides参数是模块要提供的附加服务提供者。映射键是服务类型。映射值是实现类型的非空列表,每个实现类型都是模块的成员和服务的实现。这种方法对于并发使用是安全的,因此允许多个代理在大约同一时间检测和更新同一个模块。
- 参数:
module- 重新定义的模块extraReads- 要读取的可能为空的附加模块集extraExports- 要导出的附加包的可能为空的映射extraOpens- 要打开的其他包的可能为空的映射extraUses- 可能为空的附加服务集extraProvides- 提供的附加服务的可能为空的map- 抛出:
IllegalArgumentException- 如果extraExports或extraOpens包含的密钥不是模块中的包;如果extraExports或extraOpens将键映射到空集合;如果extraProvidesmap中的值包含不是模块成员或服务实现的服务提供者类型;或extraProvides将键映射到空列表UnmodifiableModuleException- 如果无法修改模块NullPointerException- 如果任何参数是null或任何集合或映射包含null键或值- 自从:
- 9
- 参见:
-
isModifiableModule
测试是否可以使用redefineModule修改模块。如果模块是可修改的,则此方法返回true。如果模块不可修改,则此方法返回false。当模块是未命名模块时,此方法始终返回true(因为重新定义未命名模块是空操作)。- 参数:
module- 测试是否可以修改的模块- 返回:
true如果模块是可修改的,否则false- 抛出:
NullPointerException- 如果模块是null- 自从:
- 9
-