指向分析报告
指向分析会生成两种报告:分析调用树和对象树。此信息由构建过程中的一个中间步骤生成,代表了调用图和堆对象图的静态分析视图。在这些图被提前编译成二进制文件并分别写入二进制堆之前,它们会在构建过程中进一步转换。
除了对整个分析范围的全面报告外,指向分析还可以生成关于为什么某些类型/方法/字段可达的的可达性报告。
调用树 #
调用树是对指向分析所看到的调用图进行的广度优先树约简。指向分析会消除对它认定在运行时不可达的方法的调用,这是基于分析的接收器类型。它还会完全消除不可达代码块中的调用,例如受始终失败的类型检查保护的代码块。调用树报告使用-H:+PrintAnalysisCallTree
命令行选项启用,可以使用-H:PrintAnalysisCallTreeType=CSV
选项将其格式化为TXT
文件(默认)或一组CSV
文件。
TXT 格式 #
使用TXT
格式时,会生成一个具有以下结构的文件
VM Entry Points
├── entry <entry-method> id=<entry-method-id>
│ ├── directly calls <callee> id=<callee-id> @bci=<invoke-bci>
│ │ └── <callee-sub-tree>
│ ├── virtually calls <callee> @bci=<invoke-bci>
│ │ ├── is overridden by <override-method-i> id=<override-method-i-id>
│ │ │ └── <callee-sub-tree>
│ │ └── is overridden by <override-method-j> id-ref=<override-method-j-id>
│ └── interfacially calls <callee> @bci=<invoke-bci>
│ ├── is implemented by <implementation-method-x> id=<implementation-method-x-id>
│ │ └── <callee-sub-tree>
│ └── is implemented by <implementation-method-y> id-ref=<implementation-method-y-id>
├── entry <entry-method> id=<entry-method-id>
│ └── <callee-sub-tree>
└── ...
<
和>
之间的标签将使用具体值展开,其余部分将按说明进行打印。方法使用<qualified-holder>.<method-name>(<qualified-parameters>):<qualified-return-type>
格式化,并进行展开,直到不再能到达任何被调用者为止。
由于这是对调用图的树约简,因此每个具体方法只展开一次。树表示会隐式地省略对已经在不同分支或之前在同一分支中已探索过的方法的调用。此限制隐式地解决了递归问题。为了传达通过树约简而丢失的信息,每个具体方法都将获得一个唯一的 ID。因此,当首次到达某个方法时,它会声明一个标识符为id=<method-id>
。随后发现同一方法时,会使用标识符引用来指向以前展开的位置:id-ref=<method-id>
。每个id=<method-id>
和id-ref=<method-id>
后面都跟着一个空格,以便于搜索。
每个调用都用调用 bci 标记:@bci=<invoke-bci>
。对于内联方法的调用,<invoke-bci>
是一个 bci 值列表,用->
分隔,从后向前枚举内联位置,一直到原始调用位置。
CSV 格式 #
使用CSV
格式时,会生成一组包含方法及其关系的原始数据的文件。具体来说,会生成三个文件——它们分别代表方法、方法调用和调用目标。call_tree_methods_*.csv
包含以下列
Id
:此方法的唯一标识符。Name
:方法的名称。Type
:声明类型。Parameters
:用空格分隔的参数类型列表。Return
:返回类型。Display
:方法的限定名称的简短版本,在可视化中很有用。Flags
:其他元数据,例如可见性修饰符、同步等等。IsEntryPoint
:如果为true
,则该方法是调用图中的入口点(根方法),否则为false
。
call_tree_invokes_*.csv
包含以下列
Id
:调用的唯一标识符。MethodId
:包含调用的方法的标识符。BytecodeIndexes
:调用的字节码索引。如果方法是内联的,它可以是一串通过->
连接的字节码索引。TargetId
:目标方法的 ID。IsDirect
:如果为true
,则调用是直接调用,否则为false
。
call_tree_targets_*.csv
包含两列:InvokeId
和TargetId
,将调用与它们的调用目标连接起来。
这些文件的目的是使原始数据能够被自定义脚本轻松地处理或导入到图数据库中。图数据库可以提供以下功能
- 对调用树图进行复杂的图形可视化,与基于文本的格式相比,提供不同的视角。
- 能够执行复杂的查询,例如,显示导致特定代码路径被包含在调用树分析中的树的子集。这种查询功能对于管理大型分析调用树至关重要。
将文件导入图数据库的过程是特定于每个数据库的。请按照图数据库提供商提供的说明操作。
对象树 #
对象树是对包含在原生二进制堆中的对象的详尽扩展。该树是通过对原生二进制堆对象图进行深度优先遍历获得的。它使用-H:+PrintImageObjectTree
选项启用。根节点要么是静态字段,要么是包含嵌入常量的方法图。打印的值是添加到原生二进制堆中的具体常量对象。生成一个具有以下结构的文件
Heap roots
├── root <root-field> value:
│ └── <value-type> id=<value-id> toString=<value-as-string> fields:
│ ├── <field-1> value=null
│ ├── <field-2> toString=<field-2-value-as-string> (expansion suppressed)
│ ├── <field-3> value:
│ │ └── <field-3-value-type> id=<field-3-value-id> toString=<field-3-value-as-string> fields:
│ │ └── <object-tree-rooted-at-field-3>
│ ├── <array-field-4> value:
│ │ └── <array-field-4-value-type> id=<array-field-4-value-id> toString=<array-field-4-value-as-string> elements (excluding null):
│ │ ├── [<index-i>] <element-index-i-value-type> id=<element-index-i-value-id> toString=<element-index-i-value-as-string> fields:
│ │ │ └── <object-tree-rooted-at-index-i>
│ │ └── [<index-j>] <element-index-j-value-type> id=<element-index-j-value-id> toString=<element-index-j-value-as-string> elements (excluding null):
│ │ └── <object-tree-rooted-at-index-j>
│ ├── <field-5> value:
│ │ └── <field-5-value-type> id-ref=<field-5-value-id> toString=<field-5-value-as-string>
│ ├── <field-6> value:
│ │ └── <field-6-value-type> id=<field-6-value-id> toString=<field-6-value-as-string> (no fields)
│ └── <array-field-7> value:
│ └── <array-field-7-value-type> id=<array-field-7-id> toString=<array-field-7-as-string> (no elements)
├── root <root-field> id-ref=<value-id> toString=<value-as-string>
├── root <root-method> value:
│ └── <object-tree-rooted-at-constant-embeded-in-the-method-graph>
└── ...
<
和>
之间的标签将使用具体值展开,其余部分将按说明进行打印。根字段使用<qualified-holder>.<field-name>:<qualified-declared-type>
格式化。非根字段使用<field-name>:<qualified-declared-type>
格式化。值类型使用<qualified-type>
格式化。根方法使用<qualified-holder>.<method-name>(<unqualified-parameters>):<qualified-return-type>
格式化。所有字段(包括 null)都会展开非数组对象。没有字段的非数组对象用(no fields)
标记。所有非 null 索引的数组对象都会展开:[<element-index>] <object-tree-rooted-at-array-element>
。空数组对象或所有元素都为 null 的数组对象用(no elements)
标记。
每个常量值只展开一次,以压缩格式。当从多个分支到达某个值时,它只在第一次展开,并获得一个标识符:id=<value-id>
。随后发现同一值时,会使用标识符引用来指向以前展开的位置:id-ref=<value-id>
。
抑制值的展开 #
某些值,例如String
、BigInteger
和基本类型数组,默认不会展开,并用(expansion suppressed)
标记。所有其他类型都默认展开。要强制抑制默认展开的类型,可以使用-H:ImageObjectTreeSuppressTypes=<comma-separated-patterns>
。要强制展开默认抑制或通过选项抑制的类型,可以使用-H:ImageObjectTreeExpandTypes=<comma-separated-patterns>
。当同时指定-H:ImageObjectTreeSuppressTypes
和-H:ImageObjectTreeExpandTypes
时,-H:ImageObjectTreeExpandTypes
具有优先权。
类似地,某些根节点,例如java.lang.Character$UnicodeBlock.map"
会打印很多字符串,根本不会展开,也会用(expansion suppressed)
标记。所有其他根节点都默认展开。要强制抑制默认展开的根节点,可以使用-H:ImageObjectTreeSuppressRoots=<comma-separated-patterns>
。要强制展开默认抑制或通过选项抑制的根节点,可以使用-H:ImageObjectTreeExpandRoots=<comma-separated-patterns>
。当同时指定-H:ImageObjectTreeSuppressRoots
和-H:ImageObjectTreeExpandRoots
时,-H:ImageObjectTreeExpandRoots
具有优先权。
以上所有抑制/展开选项都接受逗号分隔的模式列表。对于类型,模式基于类型的完全限定名称,并引用常量的具体类型。(对于数组类型,指定元素类型就足够了,它将匹配所有该类型的数组,无论其维度如何。)对于根节点,模式基于根节点的字符串格式,如上所述。模式接受*
修饰符
- 以结尾:
*<str>
- 模式精确匹配所有以<str>
结尾的条目 - 以开头:
<str>*
- 模式精确匹配所有以<str>
开头的条目 - 包含:
*<str>*
- 模式精确匹配所有包含<str>
的条目 - 等于:
<str>
- 模式精确匹配所有等于<str>
的条目 - 全部:
*
- 模式匹配所有条目
示例
类型抑制/展开
-H:ImageObjectTreeSuppressTypes=java.io.BufferedWriter
- 抑制对java.io.BufferedWriter
对象的展开-H:ImageObjectTreeSuppressTypes=java.io.BufferedWriter,java.io.BufferedOutputStream
- 抑制对java.io.BufferedWriter
和java.io.BufferedOutputStream
对象的展开-H:ImageObjectTreeSuppressTypes=java.io.*
- 抑制对所有java.io.*
对象的展开-H:ImageObjectTreeExpandTypes=java.lang.String
- 强制展开java.lang.String
对象-H:ImageObjectTreeExpandTypes=java.lang.String,java.math.BigInteger
- 强制展开java.lang.String
和java.math.BigInteger
对象-H:ImageObjectTreeExpandTypes=java.lang.*
- 强制展开所有java.lang.*
对象-H:ImageObjectTreeSuppressTypes=java.io.* -H:ImageObjectTreeExpandTypes=java.io.PrintStream
- 抑制对所有java.io.*
的展开,但不对java.io.PrintStream
对象进行展开-H:ImageObjectTreeExpandTypes=*
- 强制展开所有类型的对象,包括默认抑制的类型
根节点抑制/展开
-H:ImageObjectTreeSuppressRoots="java.nio.charset.Charset.lookup(String)"
- 抑制对所有嵌入在com.oracle.svm.core.amd64.FrameAccess.wordSize()
图中的常量的展开-H:ImageObjectTreeSuppressRoots=java.util.*
- 抑制所有以java.util.
开头的根节点的展开。-H:ImageObjectTreeExpandRoots=java.lang.Character$UnicodeBlock.map
- 强制展开java.lang.Character$UnicodeBlock.map
静态字段根节点。-H:ImageObjectTreeSuppressRoots=java.util.* -H:ImageObjectTreeExpandRoots=java.util.Locale
- 抑制所有以java.util.
开头的根节点的展开,但java.util.Locale
除外。-H:ImageObjectTreeExpandRoots=*
- 强制展开所有根节点,包括默认情况下被抑制的那些。
可达性报告 #
在诊断代码大小或安全问题时,开发人员通常需要了解为什么某些代码元素(类型/方法/字段)是可达的。可达性报告是为了这个目的而设计的。有三种选择可以分别诊断类型、方法和字段的可达性原因。
-H:AbortOnTypeReachable=<pattern>
-H:AbortOnMethodReachable=<pattern>
-H:AbortOnFieldReachable=<pattern>
对于每个选项,右侧指定要诊断的代码元素的模式。
- 指定类型和字段的语法与抑制/扩展的语法相同(参见上面
-H:ImageObjectTreeSuppressTypes
的文档)。 - 指定方法的语法与方法过滤器的语法相同(参见
-Djdk.graal.MethodFilter
的文档)。
当其中一个选项被启用并且相应的代码元素是可达的时,将把可达性跟踪信息转储到一个 TXT 文件,并且 Native Image 会停止。以下是一个 -H:AbortOnTypeReachable=java.io.File
的可达性报告示例。
Type java.io.File is marked as allocated
at virtual method com.oracle.svm.core.jdk.NativeLibrarySupport.loadLibraryRelative(NativeLibrarySupport.java:105), implementation invoked
├── at virtual method com.oracle.svm.core.jdk.JNIPlatformNativeLibrarySupport.loadJavaLibrary(JNIPlatformNativeLibrarySupport.java:44), implementation invoked
│ ├── at virtual method com.oracle.svm.core.posix.PosixNativeLibrarySupport.loadJavaLibrary(PosixNativeLibraryFeature.java:117), implementation invoked
│ │ ├── at virtual method com.oracle.svm.core.posix.PosixNativeLibrarySupport.initializeBuiltinLibraries(PosixNativeLibraryFeature.java:98), implementation invoked
│ │ │ ├── at static method com.oracle.svm.core.graal.snippets.CEntryPointSnippets.initializeIsolate(CEntryPointSnippets.java:346), implementation invoked
│ │ │ │ str: static root method
│ │ │ └── type com.oracle.svm.core.posix.PosixNativeLibrarySupport is marked as in-heap
│ │ │ scanning root com.oracle.svm.core.posix.PosixNativeLibrarySupport@4839bf0d: com.oracle.svm.core.posix.PosixNativeLibrarySupport@4839bf0d embedded in
│ │ │ org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java)
│ │ │ at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified
│ │ │ at static method com.oracle.svm.core.graal.snippets.CEntryPointSnippets.createIsolate(CEntryPointSnippets.java:209), implementation invoked
│ │ └── type com.oracle.svm.core.posix.PosixNativeLibrarySupport is marked as in-heap
│ └── type com.oracle.svm.core.jdk.JNIPlatformNativeLibrarySupport is reachable
└── type com.oracle.svm.core.jdk.NativeLibrarySupport is marked as in-heap
scanning root com.oracle.svm.core.jdk.NativeLibrarySupport@6e06bbea: com.oracle.svm.core.jdk.NativeLibrarySupport@6e06bbea embedded in
org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java)
at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified
报告文件 #
报告生成在 reports
子目录中,相对于构建目录。在执行 native-image
可执行文件时,构建目录默认为工作目录,可以使用 -H:Path=<dir>
选项修改。
调用树报告名称的结构为 call_tree_<binary_name>_<date_time>.txt
(使用 TXT
格式)或 call_tree_*_<binary_name>_<date_time>.csv
(使用 CSV
格式)。当生成 CSV
格式的调用树报告时,也会生成遵循结构 call_tree_*.csv
的符号链接,指向最新的调用树 CSV 报告。对象树报告名称的结构为:object_tree_<binary_name>_<date_time>.txt
。二进制名称是生成的二进制文件的名称,可以使用 -H:Name=<name>
选项设置。<date_time>
采用 yyyyMMdd_HHmmss
格式。
可达性报告也位于 reports 目录中。它们遵循相同的命名约定。
- 类型可达性报告:
trace_types_<binary_name>_<date_time>.txt
- 方法可达性报告:
trace_methods_<binary_name>_<date_time>.txt
- 字段可达性报告:
trace_fields_<binary_name>_<date_time>.txt