Module java.instrument

Package java.lang.instrument


package java.lang.instrument
提供了允许Java编程语言代理在Java虚拟机(JVM)上运行的程序的服务。仪器化的机制是修改方法的字节码。

组成代理的类文件被打包到一个JAR文件中,可以与应用程序一起打包到可执行的JAR中,或者更常见的是,作为一个称为代理JAR的单独JAR文件。 JAR文件的主清单中的属性标识JAR文件中的一个类文件为代理类。 代理类定义了一个特殊的方法,JVM调用该方法来启动代理。

与应用程序一起打包在可执行JAR文件中的代理在JVM启动时启动。打包到代理JAR文件中的代理可以通过命令行选项在JVM启动时启动,或者在实现支持的情况下,在运行的JVM中启动。

代理可以在加载时以任意方式转换类,转换模块,或者转换已加载类的方法的字节码。 部署代理的开发人员或管理员,部署打包了代理的应用程序,或使用加载代理到运行中应用程序的工具的开发人员负责验证每个代理的可信度,包括代理JAR文件的内容和结构。

启动代理

在可执行JAR文件中打包的应用程序中启动代理

JAR文件规范定义了作为可执行JAR文件打包的独立应用程序的清单属性。 如果实现支持以可执行JAR的方式启动应用程序的机制,则JAR文件的主清单可以包含Launcher-Agent-Class属性,以指定与应用程序一起打包的Java代理类的二进制名称。 如果属性存在,则JVM通过加载代理类并调用其agentmain方法来启动代理。 该方法在调用应用程序的main方法之前调用。 agentmain方法有两种可能的签名之一。 JVM首先尝试在代理类上调用以下方法:

public static void agentmain(String agentArgs, Instrumentation inst)

如果代理类未定义此方法,则JVM将尝试调用:

public static void agentmain(String agentArgs)

agentArgs参数的值始终为空字符串。 在第一种方法中,inst参数是代理可以使用的Instrumentation对象来仪器化代码。

agentmain方法应执行启动代理所需的任何必要初始化并返回。 如果无法启动代理,例如代理类无法加载,代理类未定义符合规范的agentmain方法,或者agentmain方法抛出未捕获的异常或错误,则JVM将在调用应用程序的main方法之前中止。

从命令行界面启动代理

如果实现提供了从命令行界面启动代理的方法,则可以通过以下命令行选项指定代理JAR:

-javaagent:<jarpath>[=<options>]
其中<jarpath>是代理JAR文件的路径,<options>是代理选项。

代理JAR文件的主清单必须包含Premain-Class属性。 该属性的值是JAR文件中代理类的二进制名称。 JVM通过加载代理类并调用其premain方法来启动代理。 该方法在调用应用程序的main方法之前调用。 premain方法有两种可能的签名之一。 JVM首先尝试在代理类上调用以下方法:

public static void premain(String agentArgs, Instrumentation inst)

如果代理类未定义此方法,则JVM将尝试调用:

public static void premain(String agentArgs)

代理通过agentArgs参数传递其代理选项。 代理选项作为单个字符串传递,任何额外的解析应由代理本身执行。 在第一种方法中,inst参数是代理可以使用的Instrumentation对象来仪器化代码。

如果无法启动代理,例如代理类无法加载,代理类未定义符合规范的premain方法,或者premain方法抛出未捕获的异常或错误,则JVM将在调用应用程序的main方法之前中止。

实现不需要提供从命令行界面启动代理的方法。 当提供时,它支持如上所述的-javaagent选项。 -javaagent选项可以在同一命令行上多次使用,从而启动多个代理。 premain方法将按照在命令行上指定代理的顺序调用。 多个代理可以使用相同的<jarpath>

代理类还可以具有一个agentmain方法,用于在代理在运行的JVM中启动时使用(见下文)。 当使用命令行选项启动代理时,不会调用agentmain方法。

在运行的JVM中启动代理

实现可以提供一种在运行的JVM中启动代理的机制(意味着在JVM启动后)。 如何启动此操作的详细信息是特定于实现的,但通常应用程序已经启动,并且其main方法已经被调用。 如果实现支持在运行的JVM中启动代理,则适用以下规则:

  1. 代理类必须打包到一个代理JAR文件中。 代理JAR文件的主清单必须包含Agent-Class属性。 该属性的值是JAR文件中代理类的二进制名称。

  2. 代理类必须定义一个公共静态agentmain方法。

  3. JVM对它尝试在运行的JVM中启动的每个代理在标准错误流上打印警告。 如果先前启动了代理(在JVM启动时,或在运行的JVM中启动),则在尝试第二次或后续启动相同代理时是否打印警告是特定于实现的。 警告可以通过特定于实现的命令行选项禁用。

    实现注意事项: 对于HotSpot VM,JVM选项-XX:+EnableDynamicAgentLoading用于选择允许将代理动态加载到运行的JVM中。 此选项在启动运行的JVM中的代理时抑制了标准错误的警告。

JVM通过加载代理类并调用其agentmain方法来启动代理。 agentmain方法有两种可能的签名之一。 JVM首先尝试在代理类上调用以下方法:

public static void agentmain(String agentArgs, Instrumentation inst)

如果代理类未定义此方法,则JVM将尝试调用:

public static void agentmain(String agentArgs)

代理通过agentArgs参数传递其代理选项。 代理选项作为单个字符串传递,任何额外的解析应由代理本身执行。 在第一种方法中,inst参数是代理可以使用的Instrumentation对象来仪器化代码。

agentmain方法应执行启动代理所需的任何必要初始化并返回。 如果无法启动代理(例如,因为无法加载代理类,或者因为代理类没有符合规范的agentmain方法),JVM不会中止。 如果agentmain方法抛出未捕获的异常,它将被忽略(但可能会被JVM记录以进行故障排除)。

代理类还可以具有一个premain方法,用于在使用命令行选项启动代理时使用。 当在运行的JVM中启动代理时,不会调用premain方法。

加载代理类和代理类可用的模块/类

从代理JAR文件加载的类由系统类加载器加载,并且是系统类加载器的未命名模块的成员。 系统类加载器通常也定义包含应用程序main方法的类。

对于代理类可见的类是对系统类加载器可见的类,最少包括:

  • 引导层中的模块导出的包中的类。 引导层是否包含所有平台模块取决于初始模块或应用程序启动方式。

  • 可以由系统类加载器(通常是类路径)定义为其未命名模块的成员的类。

  • 代理安排由引导类加载器定义为其未命名模块的成员的任何类。

如果代理类需要链接到不在引导层中的平台(或其他)模块中的类,则应用程序可能需要以确保这些模块在引导层中的方式启动。 例如,在JDK实现中,可以使用--add-modules命令行选项将模块添加到启动时要解析的根模块集。

代理安排由引导类加载器加载的支持类(通过appendToBootstrapClassLoaderSearch或下面指定的Boot-Class-Path属性),必须仅链接到由引导类加载器定义的类。 不能保证所有平台类都可以由引导类加载器定义。

如果配置了自定义系统类加载器(通过系统属性java.system.class.loader,如getSystemClassLoader方法中指定的),则必须按照appendToSystemClassLoaderSearch中指定的定义appendToClassPathForInstrumentation方法。换句话说,自定义系统类加载器必须支持将代理JAR文件添加到系统类加载器搜索的机制。

JAR文件清单属性

应用程序或代理JAR文件清单主部分中定义的以下属性适用于Java代理:

Launcher-Agent-Class
如果实现支持在可执行JAR文件中启动应用程序的机制,则如果存在此属性,则指定与应用程序打包在一起的代理类的二进制名称。通过调用代理类的agentmain方法启动代理。在调用应用程序的main方法之前调用它。
Premain-Class
如果在JVM启动时指定了代理JAR,则此属性指定JAR文件中代理类的二进制名称。通过调用代理类的premain方法启动代理。在调用应用程序的main方法之前调用它。如果属性不存在,则JVM将中止。
Agent-Class
如果实现支持在JVM启动后的某个时刻启动代理,则此属性指定代理JAR文件中Java代理类的二进制名称。通过调用代理类的agentmain方法启动代理。此属性是必需的;如果不存在,则代理将不会启动。
Boot-Class-Path
由引导类加载器搜索的路径列表。路径表示目录或库(在许多平台上通常称为JAR或zip库)。在平台特定的定位类的机制失败后,引导类加载器将搜索这些路径。按照列出的顺序搜索这些路径。列表中的路径由一个或多个空格分隔。路径采用分层URI的路径组件的语法。如果路径以斜杠字符('/')开头,则路径是绝对的,否则是相对的。相对路径相对于代理JAR文件的绝对路径解析。格式错误和不存在的路径将被忽略。当在JVM启动后的某个时刻启动代理时,不代表JAR文件的路径将被忽略。此属性是可选的。
Can-Redefine-Classes
布尔值(truefalse,大小写不敏感)。是否需要重新定义此代理所需的类的能力。除true之外的值被视为false。此属性是可选的,默认值为false
Can-Retransform-Classes
布尔值(truefalse,大小写不敏感)。是否需要重新转换此代理所需的类的能力。除true之外的值被视为false。此属性是可选的,默认值为false
Can-Set-Native-Method-Prefix
布尔值(truefalse,大小写不敏感)。是否需要设置此代理所需的本机方法前缀的能力。除true之外的值被视为false。此属性是可选的,默认值为false

代理JAR文件的清单中可能同时存在Premain-ClassAgent-Class属性。当使用-javaagent选项在命令行上启动代理时,Premain-Class属性指定代理类的二进制名称,Agent-Class属性将被忽略。类似地,如果代理在JVM启动后的某个时刻启动,则Agent-Class属性指定代理类的二进制名称(Premain-Class属性的值将被忽略)。

在模块中为代码添加仪器

为了帮助在引导类加载器的搜索路径或加载主代理类的类加载器的搜索路径上部署支持类的代理,Java虚拟机安排转换类的模块读取这两个类加载器的未命名模块。

自Java版本:
1.5