Module java.instrument

Interface Instrumentation


public interface Instrumentation
这个类提供了在Java编程语言代码中进行仪器化所需的服务。仪器化是向方法添加字节码,以便收集数据供工具使用的过程。由于这些更改纯粹是附加的,这些工具不会修改应用程序的状态或行为。这些良性工具的示例包括监控代理、分析器、覆盖率分析器和事件记录器。

有两种方法可以获取Instrumentation接口的实例:

  1. 当以指示代理类的方式启动JVM时。在这种情况下,一个Instrumentation实例会传递给代理类的premain方法。

  2. 当JVM提供一种机制在JVM启动后的某个时刻启动代理时。在这种情况下,一个Instrumentation实例会传递给代理代码的agentmain方法。

这些机制在包规范中有描述。

一旦代理获取了Instrumentation实例,代理可以随时调用实例上的方法。

API 注意:
此接口不打算在java.instrument模块之外实现。
自从:
1.5
  • Method Details

    • addTransformer

      void addTransformer(ClassFileTransformer transformer, boolean canRetransform)
      注册提供的转换器。所有未来的类定义都将被转换器看到,除了任何已注册转换器依赖的类定义。当加载类时,当它们被重新定义时,如果canRetransform为true,则会调用转换器,如果canRetransform为true,则会调用转换器。 ClassFileTransformer定义了转换调用的顺序。如果转换器在执行过程中抛出异常,JVM仍将按顺序调用其他已注册的转换器。同一个转换器可以被添加多次,但强烈不建议这样做--通过创建转换器类的新实例来避免这种情况。

      此方法旨在用于仪器化,如类规范中所述。

      参数:
      transformer - 要注册的转换器
      canRetransform - 这个转换器的转换是否可以重新转换
      抛出:
      NullPointerException - 如果传递了一个null转换器
      UnsupportedOperationException - 如果canRetransform为true且JVM的当前配置不允许重新转换(isRetransformClassesSupported()为false)
      自从:
      1.6
    • addTransformer

      void addTransformer(ClassFileTransformer transformer)
      注册提供的转换器。

      addTransformer(transformer, false)相同。

      参数:
      transformer - 要注册的转换器
      抛出:
      NullPointerException - 如果传递了一个null转换器
      参见:
    • removeTransformer

      boolean removeTransformer(ClassFileTransformer transformer)
      注销提供的转换器。未来的类定义将不会显示给转换器。删除最近添加的匹配转换器实例。由于类加载的多线程性质,转换器可能在被移除后仍会接收调用。转换器应该被写成防御性地期望这种情况。
      参数:
      transformer - 要注销的转换器
      返回:
      如果找到并移除了转换器,则为true,如果未找到转换器,则为false
      抛出:
      NullPointerException - 如果传递了一个null转换器
    • isRetransformClassesSupported

      boolean isRetransformClassesSupported()
      返回当前JVM配置是否支持类的重新转换。重新转换已加载的类的能力是JVM的可选功能。只有在代理JAR文件中的Can-Retransform-Classes清单属性设置为true(如包规范中所述)并且JVM支持此功能时,才支持重新转换。在单个JVM的单个实例化期间,对此方法的多次调用将始终返回相同的答案。
      返回:
      如果当前JVM配置支持类的重新转换,则为true,否则为false。
      自从:
      1.6
      参见:
    • retransformClasses

      void retransformClasses(Class<?>... classes) throws UnmodifiableClassException
      重新转换提供的类集。

      此函数便于对已加载的类进行仪器化。当类最初加载或当它们被重新定义时,可以使用ClassFileTransformer来转换初始类文件字节。此函数重新运行转换过程(无论之前是否已发生转换)。此重新转换遵循以下步骤:

      • 从初始类文件字节开始
      • 对于每个使用canRetransform为false添加的转换器,上次类加载或重新定义期间transform返回的字节将作为转换的输出重用;请注意,这等效于重新应用先前的转换,未更改;只是不调用transform方法。
      • 对于每个使用canRetransform为true添加的转换器,在这些转换器中调用transform方法
      • 转换后的类文件字节将安装为类的新定义

      转换顺序在ClassFileTransformer中描述。相同的顺序用于自动重新应用不支持重新转换的转换。

      初始类文件字节代表传递给ClassLoader.defineClassredefineClasses的字节(在应用任何转换之前),但它们可能不完全匹配。常量池的布局或内容可能不相同。常量池可能具有更多或更少的条目。常量池条目可能按不同顺序排列;但是,方法的字节码中的常量池索引将对应。某些属性可能不存在。在不重要的情况下,例如方法的顺序,可能不会保留顺序。

      此方法操作一个集合,以允许同时对多个类进行相互依赖的更改(对类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

      boolean isModifiableClass(Class<?> theClass)
      测试类是否可通过重新转换重新定义。如果类可修改,则此方法返回true。如果类不可修改,则此方法返回false

      要重新转换类,isRetransformClassesSupported()也必须为true。但是,isRetransformClassesSupported()的值不影响此函数返回的值。要重新定义类,isRedefineClassesSupported()也必须为true。但是,isRedefineClassesSupported()的值不影响此函数返回的值。

      原始类(例如,java.lang.Integer.TYPE)和数组类永远不可修改。

      参数:
      theClass - 要检查是否可修改的类
      返回:
      参数类是否可修改
      抛出:
      NullPointerException - 如果指定的类为 null
      自版本:
      1.6
      参见:
    • getAllLoadedClasses

      Class[] getAllLoadedClasses()
      返回JVM当前加载的所有类的数组。返回的数组包括所有类和接口,包括所有类型的隐藏类或接口和数组类。
      返回:
      包含JVM加载的所有类的数组,如果没有则为零长度
    • getInitiatedClasses

      Class[] getInitiatedClasses(ClassLoader loader)
      返回loader可以通过ClassLoader::loadClassClass::forName和字节码链接找到的所有类的数组。也就是说,所有loader已记录为初始加载器的类。如果提供的loadernull,则返回可以由引导类加载器按名称找到的类。

      返回的数组不包括隐藏类或接口或其元素类型隐藏类或接口的数组类,因为它们无法被任何类加载器发现。

      参数:
      loader - 要返回其已初始化类列表的加载器
      返回:
      包含loader可以按名称找到的所有类的数组;如果没有则为零长度
    • getObjectSize

      long getObjectSize(Object objectToSize)
      返回指定对象消耗的存储空间的实现特定近似值。结果可能包括对象的一部分或全部开销,因此对于在一个实现内部进行比较很有用,但在不同实现之间则不适用。在JVM的单个调用期间,估计可能会发生变化。
      参数:
      objectToSize - 要计算大小的对象
      返回:
      指定对象消耗的存储空间的实现特定近似值
      抛出:
      NullPointerException - 如果提供的对象为 null
    • appendToBootstrapClassLoaderSearch

      void appendToBootstrapClassLoaderSearch(JarFile jarfile)
      指定一个包含由引导类加载器定义的仪器类的JAR文件。

      当虚拟机内置的类加载器,即“引导类加载器”,在搜索类时失败时,将搜索JAR文件中的条目。

      此方法可以多次使用,以按照调用此方法的顺序添加多个要搜索的JAR文件。

      代理应注意确保JAR文件不包含任何除了为仪器目的而由引导类加载器定义的类或资源。不遵守此警告可能导致难以诊断的意外行为。例如,假设存在一个加载器L,L的委托父级是引导类加载器。此外,类C中的一个方法,由L定义的类,引用了一个非公共访问器类C$1。如果JAR文件包含一个类C$1,那么委托给引导类加载器将导致C$1由引导类加载器定义。在这个例子中,将抛出一个IllegalAccessError,可能导致应用程序失败。避免这些问题的一种方法是为仪器类使用唯一的包名称。

      Java虚拟机规范规定,对先前未能成功解析的符号引用进行后续解析的尝试总是会失败,并且会以与初始解析尝试导致的错误相同的错误失败。因此,如果JAR文件包含与Java虚拟机先前未能成功解析引用的类对应的条目,则后续尝试解析该引用将以与初始尝试相同的错误失败。

      参数:
      jarfile - 当引导类加载器在搜索类时失败时要搜索的JAR文件。
      抛出:
      NullPointerException - 如果jarfilenull
      自版本:
      1.6
      参见:
    • appendToSystemClassLoaderSearch

      void appendToSystemClassLoaderSearch(JarFile jarfile)
      指定一个包含由系统类加载器定义的插装类的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 - 如果jarfilenull
      自从:
      1.6
      参见:
    • isNativeMethodPrefixSupported

      boolean isNativeMethodPrefixSupported()
      返回当前JVM配置是否支持设置本地方法前缀。设置本地方法前缀的能力是JVM的可选功能。只有在代理JAR文件中的Can-Set-Native-Method-Prefix清单属性设置为true(如包规范中所述)并且JVM支持此功能时,才支持设置本地方法前缀。在单个JVM的单个实例化期间,对此方法的多次调用将始终返回相同的答案。
      返回:
      如果当前JVM配置支持设置本地方法前缀,则返回true,否则返回false。
      自从:
      1.6
      参见:
    • setNativeMethodPrefix

      void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)
      该方法通过允许在名称上应用前缀重试来修改本地方法解析的失败处理。与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 - 如果extraExportsextraOpens包含不是模块中的包的键;如果extraExportsextraOpens将键映射到空集;如果extraProvides映射中的值包含不是模块成员或服务实现的服务提供者类型;或extraProvides将键映射到空列表
      UnmodifiableModuleException - 如果无法修改模块
      NullPointerException - 如果任何参数为null或任何集合中包含null键或值
      自版本:
      9
      参见:
    • isModifiableModule

      boolean isModifiableModule(Module module)
      测试模块是否可以使用redefineModule进行修改。如果模块可修改,则此方法返回true。如果模块不可修改,则此方法返回false。当模块是未命名模块时,此方法始终返回true(因为重新定义未命名模块是无操作)。
      参数:
      module - 要测试是否可修改的模块
      返回:
      true如果模块可修改,否则false
      抛出:
      NullPointerException - 如果模块为null
      自版本:
      9