使用跟踪代理收集元数据

Native Image 工具依赖于应用程序在运行时可达代码的静态分析。但是,分析无法始终完全预测 Java 本机接口 (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 或由 Native Image 直接支持的 Java 类库部分(例如 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 类库查找的内置过滤器,但这通常会导致生成的元数据文件不适合构建。类似地,可以使用 no-builtin-heuristic-filter 来禁用基于启发式方法的 Java VM 内部访问的内置过滤器,这通常会导致生成的元数据文件不可用。例如:-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)。可以通过 -H:ConfigurationResourceRoots=path/to/resources/ 提供一个包含相同元数据文件的目录,该目录位于类路径上,但不位于 META-INF/native-image/ 中。-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 工具接口 (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

进一步阅读 #

联系我们