有两种方法可以获取Instrumentation
接口的实例:
-
当以指示代理类的方式启动JVM时。在这种情况下,一个
Instrumentation
实例会传递给代理类的premain
方法。 -
当JVM提供一种机制在JVM启动后的某个时刻启动代理时。在这种情况下,一个
Instrumentation
实例会传递给代理代码的agentmain
方法。
这些机制在包规范中有描述。
一旦代理获取了Instrumentation
实例,代理可以随时调用实例上的方法。
- API 注意:
- 此接口不打算在java.instrument模块之外实现。
- 自从:
- 1.5
-
Method Summary
Modifier and TypeMethodDescriptionvoid
addTransformer
(ClassFileTransformer transformer) 注册提供的转换器。void
addTransformer
(ClassFileTransformer transformer, boolean canRetransform) 注册提供的转换器。void
指定一个包含由引导类加载器定义的仪器类的JAR文件。void
appendToSystemClassLoaderSearch
(JarFile jarfile) 指定一个包含由系统类加载器定义的仪器类的JAR文件。Class[]
返回当前由JVM加载的所有类的数组。Class[]
getInitiatedClasses
(ClassLoader loader) long
getObjectSize
(Object objectToSize) 返回指定对象消耗的存储空间的实现特定近似值。boolean
isModifiableClass
(Class<?> theClass) boolean
isModifiableModule
(Module module) 测试一个模块是否可以使用redefineModule
进行修改。boolean
返回当前JVM配置是否支持设置本地方法前缀。boolean
返回当前JVM配置是否支持类的重新定义。boolean
返回当前JVM配置是否支持类的重新转换。void
redefineClasses
(ClassDefinition... definitions) 使用提供的类文件重新定义一组类。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) 重新定义一个模块以扩展其读取的模块集、导出或打开的包集,或使用或提供的服务集。boolean
removeTransformer
(ClassFileTransformer transformer) 注销提供的转换器。void
retransformClasses
(Class<?>... classes) 重新转换提供的类集。void
setNativeMethodPrefix
(ClassFileTransformer transformer, String prefix) 通过允许对名称应用前缀来修改本地方法解析的失败处理的方法。
-
Method Details
-
addTransformer
注册提供的转换器。所有未来的类定义都将被转换器看到,除了任何已注册转换器依赖的类定义。当加载类时,当它们被重新定义时,如果canRetransform
为true,则会调用转换器,如果canRetransform
为true,则会调用转换器。ClassFileTransformer
定义了转换调用的顺序。如果转换器在执行过程中抛出异常,JVM仍将按顺序调用其他已注册的转换器。同一个转换器可以被添加多次,但强烈不建议这样做--通过创建转换器类的新实例来避免这种情况。此方法旨在用于仪器化,如类规范中所述。
- 参数:
-
transformer
- 要注册的转换器 -
canRetransform
- 这个转换器的转换是否可以重新转换 - 抛出:
-
NullPointerException
- 如果传递了一个null
转换器 -
UnsupportedOperationException
- 如果canRetransform
为true且JVM的当前配置不允许重新转换(isRetransformClassesSupported()
为false) - 自从:
- 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
重新转换提供的类集。此函数便于对已加载的类进行仪器化。当类最初加载或当它们被重新定义时,可以使用
ClassFileTransformer
来转换初始类文件字节。此函数重新运行转换过程(无论之前是否已发生转换)。此重新转换遵循以下步骤:- 从初始类文件字节开始
- 对于每个使用
canRetransform
为false添加的转换器,上次类加载或重新定义期间transform
返回的字节将作为转换的输出重用;请注意,这等效于重新应用先前的转换,未更改;只是不调用transform
方法。 - 对于每个使用
canRetransform
为true添加的转换器,在这些转换器中调用transform
方法 - 转换后的类文件字节将安装为类的新定义
转换顺序在
ClassFileTransformer
中描述。相同的顺序用于自动重新应用不支持重新转换的转换。初始类文件字节代表传递给
ClassLoader.defineClass
或redefineClasses
的字节(在应用任何转换之前),但它们可能不完全匹配。常量池的布局或内容可能不相同。常量池可能具有更多或更少的条目。常量池条目可能按不同顺序排列;但是,方法的字节码中的常量池索引将对应。某些属性可能不存在。在不重要的情况下,例如方法的顺序,可能不会保留顺序。此方法操作一个集合,以允许同时对多个类进行相互依赖的更改(对类A的重新转换可能需要对类B进行重新转换)。
如果重新转换的方法具有活动的堆栈帧,则这些活动帧将继续运行原始方法的字节码。重新转换的方法将在新调用中使用。
此方法不会引起任何初始化,除非按照惯例的JVM语义发生。换句话说,重新定义类不会导致其初始化程序运行。静态变量的值将保持调用之前的状态。
重新转换的类的实例不受影响。
支持的类文件更改在JVM TI RetransformClasses中描述。在应用转换之后,类文件字节不会被检查、验证和安装,如果结果字节错误,此方法将抛出异常。
如果此方法抛出异常,则没有类被重新转换。
此方法旨在用于仪器化,如类规范中所述。
- 参数:
-
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 RedefineClasses中描述。在应用转换之后,类文件字节不会被检查、验证和安装,如果结果字节错误,此方法将抛出异常。
如果此方法抛出异常,则没有类被重新定义。
此方法旨在用于仪器化,如类规范中所述。
- 参数:
-
definitions
- 要重新定义的类数组及其相应的定义;允许长度为零的数组,在这种情况下,此方法不执行任何操作 - 抛出:
-
UnmodifiableClassException
- 如果无法修改指定的类(isModifiableClass(java.lang.Class<?>)
将返回false
) -
UnsupportedOperationException
- 如果JVM的当前配置不允许重新定义(isRedefineClassesSupported()
为false)或尝试进行不支持的更改 -
ClassFormatError
- 如果数据不包含有效的类 -
NoClassDefFoundError
- 如果类文件中的名称与类的名称不相等 -
UnsupportedClassVersionError
- 如果类文件版本号不受支持 -
ClassCircularityError
- 如果新类包含循环依赖 -
LinkageError
- 如果发生链接错误 -
NullPointerException
- 如果提供的定义数组或其任何组件为null
-
ClassNotFoundException
- 永远不会抛出(仅出于兼容性原因而存在) - 参见:
-
isModifiableClass
测试类是否可通过重新转换或重新定义。如果类可修改,则此方法返回true
。如果类不可修改,则此方法返回false
。要重新转换类,
isRetransformClassesSupported()
也必须为true。但是,isRetransformClassesSupported()
的值不影响此函数返回的值。要重新定义类,isRedefineClassesSupported()
也必须为true。但是,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文件
中的条目。此方法可以多次使用,以按照调用此方法的顺序添加多个要搜索的JAR文件。
代理应注意确保JAR文件不包含任何除了为仪器目的而由引导类加载器定义的类或资源。不遵守此警告可能导致难以诊断的意外行为。例如,假设存在一个加载器L,L的委托父级是引导类加载器。此外,类C中的一个方法,由L定义的类,引用了一个非公共访问器类C$1。如果JAR文件包含一个类C$1,那么委托给引导类加载器将导致C$1由引导类加载器定义。在这个例子中,将抛出一个
IllegalAccessError
,可能导致应用程序失败。避免这些问题的一种方法是为仪器类使用唯一的包名称。Java虚拟机规范规定,对先前未能成功解析的符号引用进行后续解析的尝试总是会失败,并且会以与初始解析尝试导致的错误相同的错误失败。因此,如果JAR文件包含与Java虚拟机先前未能成功解析引用的类对应的条目,则后续尝试解析该引用将以与初始尝试相同的错误失败。
- 参数:
-
jarfile
- 当引导类加载器在搜索类时失败时要搜索的JAR文件。 - 抛出:
-
NullPointerException
- 如果jarfile
为null
。 - 自版本:
- 1.6
- 参见:
-
appendToSystemClassLoaderSearch
指定一个包含由系统类加载器定义的插装类的JAR文件。当系统类加载器委托(参见getSystemClassLoader()
)无法搜索到一个类时,也会搜索JarFile
中的条目。此方法可以多次使用,以按照调用此方法的顺序添加多个要搜索的JAR文件。
代理应确保JAR文件不包含除了系统类加载器为插装目的定义的类或资源之外的任何类或资源。不遵守此警告可能导致难以诊断的意外行为(参见
appendToBootstrapClassLoaderSearch
)。系统类加载器支持添加要搜索的JAR文件,如果它实现了一个名为
appendToClassPathForInstrumentation
的方法,该方法接受一个类型为java.lang.String
的单个参数。该方法不需要具有public
访问权限。通过在jarfile
上调用getName()
方法获取JAR文件的名称,并将其作为参数提供给appendToClassPathForInstrumentation
方法。Java虚拟机规范规定,对先前未能成功解析的符号引用进行后续解析的尝试总是会以与初始解析尝试导致的错误相同的错误失败。因此,如果JAR文件包含与Java虚拟机先前未能成功解析引用的类对应的条目,则后续尝试解析该引用将以与初始尝试相同的错误失败。
此方法不会更改
java.class.path
的值系统属性
。- 参数:
-
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) { ... 记录对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
- 在重试失败的本地方法解析时应用于包装本地方法的前缀。如果前缀为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
参数是要打开的附加包的映射。在这两种情况下,映射键是在Java语言规范第6.5.3节中定义的包的完全限定名称,例如,"java.lang"
。映射值是应将该包导出或打开到的非空模块集。extraProvides
参数是模块提供的附加服务提供者。映射键是服务类型。映射值是非空的实现类型列表,每个类型都是模块的成员,并且是服务的实现。此方法支持并发使用,因此允许多个代理在大致相同的时间内对同一模块进行插装和更新。
- 参数:
-
module
- 要重新定义的模块 -
extraReads
- 可能为空的额外要读取的模块集合 -
extraExports
- 可能为空的额外要导出的包的映射 -
extraOpens
- 可能为空的额外要开放的包的映射 -
extraUses
- 可能为空的额外要使用的服务集合 -
extraProvides
- 可能为空的额外要提供的服务的映射 - 抛出:
-
IllegalArgumentException
- 如果extraExports
或extraOpens
包含不是模块中的包的键;如果extraExports
或extraOpens
将键映射到空集;如果extraProvides
映射中的值包含不是模块成员或服务实现的服务提供者类型;或extraProvides
将键映射到空列表 -
UnmodifiableModuleException
- 如果无法修改模块 -
NullPointerException
- 如果任何参数为null
或任何集合中包含null
键或值 - 自版本:
- 9
- 参见:
-
isModifiableModule
测试模块是否可以使用redefineModule
进行修改。如果模块可修改,则此方法返回true
。如果模块不可修改,则此方法返回false
。当模块是未命名模块时,此方法始终返回true
(因为重新定义未命名模块是无操作)。- 参数:
-
module
- 要测试是否可修改的模块 - 返回:
-
true
如果模块可修改,否则false
- 抛出:
-
NullPointerException
- 如果模块为null
- 自版本:
- 9
-