public interface Instrumentation
有两种获取Instrumentation
接口实例的方法:
当以指示代理类的方式启动JVM时。 在这种情况下, Instrumentation
实例被传递给代理类的premain
方法。
当JVM在JVM启动后的某个时间提供启动代理的机制时。 在这种情况下, Instrumentation
实例将传递给代理代码的agentmain
方法。
这些机制在package specification中有描述 。
一旦代理商获得一个Instrumentation
实例,代理可以随时调用实例上的方法。
Modifier and Type | Method and Description |
---|---|
void |
addTransformer(ClassFileTransformer transformer)
注册提供的变压器。
|
void |
addTransformer(ClassFileTransformer transformer, boolean canRetransform)
注册提供的变压器。
|
void |
appendToBootstrapClassLoaderSearch(JarFile jarfile)
指定具有由引导类加载器定义的检测类的JAR文件。
|
void |
appendToSystemClassLoaderSearch(JarFile jarfile)
指定具有由系统类加载器定义的检测类的JAR文件。
|
类[] |
getAllLoadedClasses()
返回当前由JVM加载的所有类的数组。
|
类[] |
getInitiatedClasses(ClassLoader loader)
返回一个
loader 是起始加载器的所有类的数组。
|
long |
getObjectSize(Object objectToSize)
返回指定对象所消耗的存储量的实现特定近似值。
|
boolean |
isModifiableClass(类<?> theClass)
确定课程是否可以通过
retransformation或
redefinition进行修改 。
|
boolean |
isNativeMethodPrefixSupported()
返回当前JVM配置是否支持
setting a native method prefix 。
|
boolean |
isRedefineClassesSupported()
返回当前的JVM配置是否支持重新定义类。
|
boolean |
isRetransformClassesSupported()
返回当前JVM配置是否支持对类进行重新传输。
|
void |
redefineClasses(ClassDefinition... definitions)
使用提供的类文件重新定义提供的一组类。
|
boolean |
removeTransformer(ClassFileTransformer transformer)
注销提供的变压器。
|
void |
retransformClasses(类<?>... classes)
重新转换提供的一组类。
|
void |
setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)
此方法通过允许使用应用于名称的前缀进行重试来修改本机方法解析的故障处理。
|
void addTransformer(ClassFileTransformer transformer, boolean canRetransform)
canRetransform
为真, 则为retransformed 。
有关变换调用的顺序,请参阅ClassFileTransformer.transform
。
如果变压器在执行过程中引发异常,则JVM依然会调用其他已注册的变压器。
相同的变压器可能不止一次添加,但强烈不鼓励 - 通过创建一个新的变压器类实例来避免这种情况。
此方法旨在用于检测使用中,当在所描述的class specification 。
transformer
- 要注册的变压器
canRetransform
- 可以将该变压器的转换重新转换
NullPointerException
- 如果通过一个
null
变压器
UnsupportedOperationException
- 如果
canRetransform
为true并且JVM的当前配置不允许转发(
isRetransformClassesSupported()
为假)
void addTransformer(ClassFileTransformer transformer)
与addTransformer(transformer, false)
相同。
transformer
- 要注册的变压器
NullPointerException
- 如果通过一个
null
变压器
addTransformer(ClassFileTransformer,boolean)
boolean removeTransformer(ClassFileTransformer transformer)
transformer
- 要注销的变压器
NullPointerException
- 如果通过一个
null
变压器
boolean isRetransformClassesSupported()
Can-Retransform-Classes
清单属性被设置为true
在代理JAR文件(如在所描述的package specification )和JVM支持此功能。
在单个JVM的单个实例化过程中,对此方法的多次调用将始终返回相同的答案。
retransformClasses(java.lang.Class<?>...)
void retransformClasses(类<?>... classes) throws UnmodifiableClassException
此功能有助于已加载类的检测。 当初始加载类或者当它们是redefined时,初始类文件字节可以用ClassFileTransformer
进行转换 。 此函数重新运行转换过程(无论以前是否发生转换)。 此转发遵循以下步骤:
canRetransform
的每个变压器,在最后一个类加载或重新定义期间由transform
返回的字节被重新用作转换的输出; 请注意,这相当于重新应用以前的转换,未改变; 除了transform
不被调用 canRetransform
的每个变压器,在这些变压器中调用transform
方法 transform
方法描述了转换顺序 。 在重新编码无法转换的自动重新应用中使用相同的顺序。
初始类文件字节表示传递给ClassLoader.defineClass
或redefineClasses
(在应用任何转换之前)的字节,但它们可能不完全匹配。 常量池可能不具有相同的布局或内容。 常量池可能有更多或更少的条目。 常量池条目可能有不同的顺序; 然而,方法的字节码中的常量池索引将对应。 某些属性可能不存在。 如果订单无意义,例如方法的顺序,订单可能不会被保留。
该方法对一组进行操作,以允许同时对多个类进行相互依赖的更改(类A的重新转换可能需要对类B进行重新转换)。
如果重新转换的方法具有活动堆栈帧,则这些活动帧将继续运行原始方法的字节码。 重新构建的方法将用于新的调用。
该方法不会导致任何初始化,除了在常规JVM语义下会发生。 换句话说,重新定义一个类并不会导致它的初始化器被运行。 静态变量的值将保持在调用之前。
转载类的实例不受影响。
重新转换可能会改变方法体,常量池和属性。 重新传输不能添加,删除或重命名字段或方法,更改方法的签名或更改继承。 这些限制可能在将来的版本中解除。 类文件字节不会被检查,验证和安装,直到应用转换为止,如果结果字节错误,则此方法将抛出异常。
如果此方法抛出异常,则不会重新创建任何类。
此方法旨在用于检测使用中,当在所描述的class specification 。
classes
- 要转发的类的数组;
允许零长度数组,在这种情况下,此方法什么也不做
UnmodifiableClassException
- 如果指定的类不能被修改(
isModifiableClass(java.lang.Class<?>)
将返回
false
)
UnsupportedOperationException
- 如果JVM的当前配置不允许重新传输(
isRetransformClassesSupported()
为false)或重新尝试进行不受支持的更改
ClassFormatError
- 如果数据不包含有效的类
NoClassDefFoundError
- 如果类文件中的名称不等于类的名称
UnsupportedClassVersionError
- 如果不支持类文件版本号
ClassCircularityError
- 如果新类包含循环
LinkageError
- 如果发生链接错误
NullPointerException
- 如果提供的类数组或其任何组件是
null
。
isRetransformClassesSupported()
, addTransformer(java.lang.instrument.ClassFileTransformer, boolean)
, ClassFileTransformer
boolean isRedefineClassesSupported()
Can-Redefine-Classes
清单属性被设置为true
在代理JAR文件(如在所描述的package specification )和JVM支持此功能。
在单个JVM的单个实例化过程中,对此方法的多次调用将始终返回相同的答案。
redefineClasses(java.lang.instrument.ClassDefinition...)
void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException
该方法用于替换类的定义,而不引用现有的类文件字节,就像从源进行重新编译以进行修复和继续调试时一样。 在现有的类文件字节要转换的地方(例如,在字节码仪器中)应该使用retransformClasses
。
该方法对一组进行操作,以便同时允许多个类的相互依赖的更改(A类的重新定义可能需要重新定义B类)。
如果重新定义的方法具有活动堆栈帧,则这些活动帧将继续运行原始方法的字节码。 重新定义的方法将用于新的调用。
该方法不会导致任何初始化,除了在常规JVM语义下会发生。 换句话说,重新定义一个类并不会导致它的初始化器被运行。 静态变量的值将保持在调用之前。
重新定义的类的实例不受影响。
重新定义可能会改变方法体,常量池和属性。 重定义不能添加,删除或重命名字段或方法,更改方法的签名或更改继承。 这些限制可能在将来的版本中解除。 类文件字节不会被检查,验证和安装,直到应用转换为止,如果结果字节错误,则此方法将抛出异常。
如果此方法抛出异常,则不会重新定义任何类。
此方法旨在用于检测使用中,当在所描述的class specification 。
definitions
- 重新定义相应定义的类的数组;
允许零长度数组,在这种情况下,此方法什么也不做
UnmodifiableClassException
- 如果指定的类不能被修改(
isModifiableClass(java.lang.Class<?>)
将返回
false
)
UnsupportedOperationException
- 如果JVM的当前配置不允许重新定义(
isRedefineClassesSupported()
为false)或重新定义尝试进行不受支持的更改
ClassFormatError
- 如果数据不包含有效的类
NoClassDefFoundError
- 如果类文件中的名称不等于类的名称
UnsupportedClassVersionError
- 如果不支持类文件版本号
ClassCircularityError
- 如果新类包含圆形
LinkageError
- 如果发生链接错误
NullPointerException
- 如果提供的定义数组或其任何组件是
null
ClassNotFoundException
- 永远不能抛出(仅出于兼容性原因出现)
isRedefineClassesSupported()
, addTransformer(java.lang.instrument.ClassFileTransformer, boolean)
, ClassFileTransformer
boolean isModifiableClass(类<?> theClass)
true
。
如果类不可修改,则此方法返回false
。
对于要转载的类, isRetransformClassesSupported()
也必须是真的。 但价值isRetransformClassesSupported()
不影响此函数返回的值。 对于要重新定义的类, isRedefineClassesSupported()
也必须是真的。 但价值isRedefineClassesSupported()
不影响此函数返回的值。
原始类(例如, java.lang.Integer.TYPE
)和数组类永远都不可修改。
theClass
- 检查可修改的类
NullPointerException
- 如果指定的类是
null
。
retransformClasses(java.lang.Class<?>...)
,
isRetransformClassesSupported()
,
redefineClasses(java.lang.instrument.ClassDefinition...)
,
isRedefineClassesSupported()
类[] getAllLoadedClasses()
类[] getInitiatedClasses(ClassLoader loader)
loader
是起始加载程序的所有类的数组。
如果提供的加载程序是null
,则返回由引导类加载器启动的类。
loader
- 将返回其启动的类列表的加载程序
long getObjectSize(Object objectToSize)
objectToSize
- 对象的大小
NullPointerException
- 如果提供的对象是
null
。
void appendToBootstrapClassLoaderSearch(JarFile jarfile)
当虚拟机的内置类加载器,被称为“引导类加载器”,未成功搜索的一类,在条目JAR file
会被搜索。
该方法可以被多次使用,以按照调用此方法的顺序添加要搜索的多个JAR文件。
代理应该注意确保JAR不包含除了引导类加载器为了进行检测而定义的类之外的任何类或资源。 不遵守此警告可能导致难以诊断的意外行为。 例如,假设有一个加载器L,并且用于委托的L''s parent是引导类加载器。 此外,类C中由L定义的类的方法引用非公共访问器类C $ 1。 如果JAR文件包含C $ 1类,则引导类加载器的委派将导致C $ 1由引导类加载器定义。 在此示例中, IllegalAccessError
将被抛出,可能导致应用程序失败。 避免这些类型问题的一种方法是使用检测类的唯一包名称。
The Java™ Virtual Machine Specification指定后续尝试解析Java虚拟机先前未成功尝试解析的符号引用始终会失败,并导致与初始解析尝试结果相同的错误。 因此,如果JAR文件包含与Java虚拟机未成功尝试解析引用的类对应的条目,则后续尝试解析该引用将失败,并且与初始尝试相同。
jarfile
- 引导类加载程序无法搜索类时要搜索的JAR文件。
NullPointerException
- 如果
jarfile
是
null
。
appendToSystemClassLoaderSearch(java.util.jar.JarFile)
, ClassLoader
, JarFile
void appendToSystemClassLoaderSearch(JarFile jarfile)
getSystemClassLoader()
)不成功地搜索类时,也将搜索JarFile
中的条目。
该方法可以被多次使用,以按照调用此方法的顺序添加要搜索的多个JAR文件。
代理应该注意确保JAR不包含系统类加载器为了进行检测而定义的任何类或资源。 不遵守此警告可能导致难以诊断的意外行为(参见appendToBootstrapClassLoaderSearch
)。
系统类加载器支持添加,如果它实现了一个命名的方法要被搜索的JAR文件appendToClassPathForInstrumentation
其中采用类型的单个参数java.lang.String
。 该方法不需要具有public
访问权限。 通过调用获得的JAR文件的名称getName()
上的方法jarfile
并且这被设置为参数的appendToClassPathForInstrumentation
方法。
The Java™ Virtual Machine Specification指定后续尝试解析Java虚拟机先前未成功尝试解析的符号引用始终会失败,并导致与初始解析尝试结果相同的错误。 因此,如果JAR文件包含与Java虚拟机未成功尝试解析引用的类对应的条目,则后续尝试解析该引用将失败,并且与初始尝试相同。
此方法不会更改java.class.path
system property
的值。
jarfile
- 当系统类加载程序不成功搜索类时要搜索的JAR文件。
UnsupportedOperationException
- 如果系统类加载器不支持追加要搜索的JAR文件。
NullPointerException
- 如果
jarfile
是
null
。
appendToBootstrapClassLoaderSearch(java.util.jar.JarFile)
, ClassLoader.getSystemClassLoader()
, JarFile
boolean isNativeMethodPrefixSupported()
Can-Set-Native-Method-Prefix
清单属性被设置为true
在代理JAR文件(如在所描述的package specification )和JVM支持此功能。
在单个JVM的单个实例化过程中,对此方法的多次调用将始终返回相同的答案。
setNativeMethodPrefix(java.lang.instrument.ClassFileTransformer, java.lang.String)
void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)
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的本机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
的封装, foo
只是$trans3_$trans1_foo
。 为了能够有效地确定前缀的顺序,只有当它的非本地包装存在时才应用中间前缀。 因此,在最后一个例子中,尽管$trans1_foo
不是本地方法,但$trans1_
前缀是$trans1_foo
存在的。
transformer
- 使用此前缀包装的ClassFileTransformer。
prefix
- 重试本机方法解析失败时应用于包装本地方法的前缀。
如果前缀为null
或空字符串,则不会为此变压器重试本机方法分辨率失败。
NullPointerException
- 如果通过一个
null
变压器。
UnsupportedOperationException
- 如果JVM的当前配置不允许设置本机方法前缀(
isNativeMethodPrefixSupported()
为假)。
IllegalArgumentException
- 如果变压器未注册(见
addTransformer
)。