使用追踪代理收集元数据

Native Image 工具依赖于对应用程序运行时可达代码的静态分析。然而,这种分析并非总能完全预测 Java Native Interface (JNI)、Java 反射、动态代理对象或类路径资源的所有用法。这些动态特性中未被检测到的用法必须以元数据(在代码中预先计算或作为 JSON 配置文件)的形式提供给 native-image 工具。

在这里,您将找到关于如何自动收集应用程序元数据并编写 JSON 配置文件的信息。要了解如何在代码中计算动态特性调用,请参阅可达性元数据

目录 #

跟踪代理 #

GraalVM 提供了一个跟踪代理,用于轻松收集元数据并准备配置文件。该代理在常规 Java VM 上执行应用程序期间跟踪动态特性的所有用法。

使用 GraalVM JDK 中的 java 命令在命令行启用代理

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ ...

注意:-agentlib 必须在 -jar 选项或类名或任何应用程序参数之前指定,作为 java 命令的一部分。

运行时,代理会查找 native-image 工具需要额外信息的类、方法、字段和资源。当应用程序完成且 JVM 退出时,代理会将元数据写入指定输出目录 (/path/to/config-dir/) 中的 JSON 文件。

为了更好地覆盖动态特性,可能需要多次运行应用程序(使用不同的执行路径)。config-merge-dir 选项会将配置添加到现有配置集合中,如下所示

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-merge-dir=/path/to/config-dir/ ...                                                              ^^^^^

代理还提供以下选项以定期写入元数据

  • config-write-period-secs=n:每 n 秒写入元数据文件;n 必须大于 0。
  • config-write-initial-delay-secs=n:首次写入元数据前等待 n 秒;默认为 1

例如

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/,config-write-period-secs=300,config-write-initial-delay-secs=5 ...

上述命令将在初始延迟 5 秒后,每 300 秒将元数据文件写入 /path/to/config-dir/

建议手动检查生成的配置文件。由于代理仅观察已执行的代码,因此应用程序输入应尽可能覆盖更多的代码路径。

生成的配置文件可以通过将其放置在类路径上的 META-INF/native-image/ 目录中来提供给 native-image 工具。此目录(或其任何子目录)会搜索名为 reachability-metadata.json 的文件,然后该文件会自动包含在构建过程中。并非所有这些文件都必须存在。当找到多个同名文件时,所有文件都会被考虑。

要测试代理在示例应用程序上收集元数据,请参阅使用反射构建原生可执行文件指南。

条件元数据收集 #

代理可以根据代码中的使用情况推断元数据条件。条件元数据主要面向库维护者,目标是减少整体占用空间。

要使用代理收集条件元数据,请参阅条件元数据收集

代理高级用法 #

基于调用者的过滤器 #

默认情况下,代理会过滤掉 Native Image 无需配置即可支持的动态访问。过滤机制通过识别执行访问的 Java 方法(也称为调用者方法),并将其声明类与一系列过滤规则进行匹配来工作。内置过滤规则从生成的配置文件中排除了源自 JVM 或 Java 类库中 Native Image 直接支持的部分(例如 java.nio)的动态访问。被访问的项(类、方法、字段、资源等)与过滤无关。

除了内置过滤器外,还可以使用 caller-filter-file 选项指定包含附加规则的自定义过滤器文件。例如:-agentlib:caller-filter-file=/path/to/filter-file,config-output-dir=...

过滤器文件具有以下结构

{ "rules": [
    {"excludeClasses": "com.oracle.svm.**"},
    {"includeClasses": "com.oracle.svm.tutorial.*"},
    {"excludeClasses": "com.oracle.svm.tutorial.HostedHelper"}
  ],
  "regexRules": [
    {"includeClasses": ".*"},
    {"excludeClasses": ".*\\$\\$Generated[0-9]+"}
  ]
}

rules 部分包含一系列规则。每个规则指定 includeClasses(表示源自匹配类的查找将包含在生成的配置中)或 excludeClasses(表示源自匹配类的查找将从配置中排除)。每个规则定义一个用于匹配类的模式。模式可以以 .*.** 结尾,解释如下:- .* 匹配包中的所有类,且仅匹配该包;- .** 匹配包中的所有类以及所有子包(任意深度)。如果没有 .*.**,则该规则仅适用于具有与模式匹配的完全限定名称的单个类。所有规则都按照它们指定的顺序进行处理,因此后面的规则可以部分或完全覆盖前面的规则。当提供多个过滤器文件时(通过指定多个 caller-filter-file 选项),它们的规则按照文件指定的顺序链接在一起。内置调用者过滤器的规则总是首先处理,因此它们可以在自定义过滤器文件中被覆盖。

在上面的例子中,第一条规则将所有源自 com.oracle.svm 包及其所有子包(及其子包等)中的类的查找从生成的元数据中排除。然而,在下一条规则中,直接位于 com.oracle.svm.tutorial 包中的类的查找又被包含进来。最后,来自 HostedHelper 类的查找又被排除。这些规则中的每一个都部分覆盖了之前的规则。例如,如果规则的顺序相反,那么 com.oracle.svm.** 的排除将是最后一条规则,并会覆盖所有其他规则。

regexRules 部分也包含一系列规则。其结构与 rules 部分相同,但规则被指定为正则表达式模式,用于匹配整个完全限定的类标识符。regexRules 部分是可选的。如果指定了 regexRules 部分,则当且仅当 rulesregexRules 都包含该类且两者均不排除它时,该类才被视为包含在内。如果没有 regexRules 部分,则只有 rules 部分决定是否包含或排除类。

出于测试目的,可以通过添加 no-builtin-caller-filter 选项来禁用 Java 类库查找的内置过滤器,但生成的元数据文件通常不适用于构建。类似地,基于启发式的 Java VM 内部访问的内置过滤器可以使用 no-builtin-heuristic-filter 禁用,这通常也会导致元数据文件可用性降低。例如:-agentlib:native-image-agent=no-builtin-caller-filter,no-builtin-heuristic-filter,config-output-dir=...

访问过滤器 #

与上述基于调用者的过滤器(根据动态访问的来源进行过滤)不同,访问过滤器适用于访问的目标。因此,访问过滤器可以直接从生成的配置中排除包和类(及其成员)。

默认情况下,所有被访问的类(也通过了基于调用者的过滤器和内置过滤器)都包含在生成的配置中。使用 access-filter-file 选项,可以添加一个遵循上述文件结构的自定义过滤器文件。该选项可以多次指定以添加多个过滤器文件,并且可以与其他过滤器选项结合使用,例如:-agentlib:access-filter-file=/path/to/access-filter-file,caller-filter-file=/path/to/caller-filter-file,config-output-dir=...

将配置文件指定为参数 #

可以通过 -H:ConfigurationFileDirectories=/path/to/config-dir/ 将包含配置文件但不在类路径中的目录指定给 native-image。此目录必须直接包含 reachability-metadata.json 或以前使用的单个元数据文件(jni-config.jsonreflect-config.jsonproxy-config.jsonserialization-config.jsonresource-config.json)。类路径上但在 META-INF/native-image/ 之外包含相同元数据文件的目录可以通过 -H:ConfigurationResourceRoots=path/to/resources/ 提供。-H:ConfigurationFileDirectories-H:ConfigurationResourceRoots 都可以接受逗号分隔的目录列表。

通过进程环境注入代理 #

如果 Java 进程是由应用程序或脚本文件启动,或者 Java 甚至嵌入在现有进程中,那么修改 java 命令行来注入代理可能会很困难。在这种情况下,也可以通过 JAVA_TOOL_OPTIONS 环境变量来注入代理。此环境变量可以被同时运行的多个 Java 进程拾取,在这种情况下,每个代理必须使用 config-output-dir 写入独立的输出目录。(下一节描述如何合并配置文件的集合。)为了在单个全局 JAVA_TOOL_OPTIONS 变量中使用不同的路径,代理的输出路径选项支持占位符

export JAVA_TOOL_OPTIONS="-agentlib:native-image-agent=config-output-dir=/path/to/config-output-dir-{pid}-{datetime}/"

{pid} 占位符被替换为进程标识符,而 {datetime} 则被替换为 UTC 格式(符合 ISO 8601)的系统日期和时间。对于上述示例,结果路径可能是:/path/to/config-output-dir-31415-20181231T235950Z/

跟踪文件 #

在上面的例子中,native-image-agent 既用于跟踪 JVM 上的动态访问,又用于从中生成一组配置文件。然而,为了更好地理解执行情况,代理还可以以 JSON 格式写入一个包含每个独立访问的跟踪文件

$JAVA_HOME/bin/java -agentlib:native-image-agent=trace-output=/path/to/trace-file.json ...

native-image-configure 工具可以将跟踪文件转换为配置文件。以下命令读取并处理 trace-file.json 并在目录 /path/to/config-dir/ 中生成一组配置文件

native-image-configure generate --trace-input=/path/to/trace-file.json --output-dir=/path/to/config-dir/

互操作性 #

代理使用 JVM Tool Interface (JVMTI),并且可以与支持 JVMTI 的其他 JVM 配合使用。在这种情况下,需要提供代理的绝对路径

/path/to/some/java -agentpath:/path/to/graalvm/jre/lib/amd64/libnative-image-agent.so=<options> ...

实验性选项 #

代理有一些目前处于实验阶段的选项,它们可能在未来版本中启用,但也可能被更改或完全删除。请参阅ExperimentalAgentOptions.md指南。

Native Image 配置工具 #

如上一节所述,当同时在多个进程中使用代理时,config-output-dir 是一个安全的选项,但这会导致生成多组配置文件。native-image-configure 工具可用于合并这些配置文件

native-image-configure generate --input-dir=/path/to/config-dir-0/ --input-dir=/path/to/config-dir-1/ --output-dir=/path/to/merged-config-dir/

此命令从 /path/to/config-dir-0/ 读取一组配置文件,从 /path/to/config-dir-1/ 读取另一组,然后将包含它们两者信息的一组配置文件写入 /path/to/merged-config-dir/。可以指定任意数量的 --input-dir 参数,每个参数带有一组配置文件。有关所有选项,请参阅 native-image-configure help

延伸阅读 #

联系我们