GraalVM Native Image 静态分析报告(Points-to Analysis Reports)完全指南:调用树、对象树与可达性追踪

发布时间:2026/9/21 0:01:52
GraalVM Native Image 静态分析报告(Points-to Analysis Reports)完全指南:调用树、对象树与可达性追踪 GraalVM Native Image 静态分析报告Points-to Analysis Reports完全指南调用树、对象树与可达性追踪【免费下载链接】graalGraalVM compiles applications into native executables that start instantly, scale fast, and use fewer compute resources 项目地址: https://gitcode.com/gh_mirrors/gr/graalGraalVM Native Image 的构建过程包含一个关键的中间步骤——点到点分析points-to analysis它基于可达性闭包对应用代码进行静态分析决定哪些类型、方法、字段与堆对象会被保留进最终的二进制映像。本文以 StaticAnalysisReports.md 为主线系统讲解该分析产生的三类诊断产物分析调用树call tree、对象树object tree与可达性报告reachability report并深入仓库源码substratevm/src/com.oracle.graal.pointsto下的reports包说明其生成机制、全部命令行选项、输出格式与实战用法。读完本文你将能熟练启用各类报告解读调用图与堆对象图的静态视图并在排查二进制体积膨胀、不必要的类被保留、以及安全相关代码意外可达等问题时用可达性追踪快速定位根因。静态分析报告概览构建过程的静态视图点到点分析产生两类核心报告分析调用树与对象树。分析调用树是分析所见调用图call graph的广度优先树约简展示各方法之间的静态调用关系对象树是对最终写入原生二进制堆native binary heap的对象图的穷举展开展示堆中保留的常量对象及其引用结构。这两类图在构建过程中由分析阶段产生属于静态分析视图此后它们还会在构建流程中被进一步变换最终分别被提前编译ahead-of-time进二进制代码与写入二进制堆。在整仓库全量分析的完整报告之外点到点分析还可以针对某个具体的类型、方法或字段输出可达性报告回答为什么这个代码元素是可达的这一诊断问题。这些报告的生成由 AnalysisReporter.printAnalysisReports 统一调度它根据各类开关分别调用CallTreePrinter.print、ObjectTreePrinter.print同时输出堆直方图以及PointsToStats.report等。所有选项的定义集中在 AnalysisReportsOptions.java 中。分析调用树Call Tree什么是调用树调用树是点到点分析所见调用图的广度优先树约简breadth-first tree reduction。与传统的调用图不同点到点分析会基于已分析出的接收者类型receiver types消除运行时不可能到达的方法调用同时位于不可达代码块例如被一个永远失败的类型检查所守卫的代码块中的调用也会被整体消除。因此这份调用树反映的是静态分析认为真正可能发生的调用而不是字节码中机械罗列的所有调用指令。通过-H:PrintAnalysisCallTree选项启用调用树报告输出格式由-H:PrintAnalysisCallTreeType指定可取TXT默认或CSV。从源码看PrintAnalysisCallTreeType是一个枚举选项CallTreeType 枚举且一旦设置该选项会自动启用PrintAnalysisCallTree。TXT 格式使用TXT格式时生成的文件具有如下结构VM Entry Points ├── entry entry-method identry-method-id │ ├── directly calls callee idcallee-id bciinvoke-bci │ │ └── callee-sub-tree │ ├── virtually calls callee bciinvoke-bci │ │ ├── is overridden by override-method-i idoverride-method-i-id │ │ │ └── callee-sub-tree │ │ └── is overridden by override-method-j id-refoverride-method-j-id │ └── interfacially calls callee bciinvoke-bci │ ├── is implemented by implementation-method-x idimplementation-method-x-id │ │ └── callee-sub-tree │ └── is implemented by implementation-method-y id-refimplementation-method-y-id ├── entry entry-method identry-method-id │ └── callee-sub-tree └── ...其中尖括号...内的标签会被替换为具体值其余文本原样输出。各元素的具体格式为方法使用qualified-holder.method-name(qualified-parameters):qualified-return-type格式例如java.util.HashMap.put(java.lang.Object,java.lang.Object):java.lang.Object。在源码中这一格式由 CallTreePrinter.METHOD_FORMAT 的%H.%n(%P):%R定义与报告内所有方法比较排序使用的格式完全一致。树会一直展开直到没有更多可到达的 callee 为止。id 与 id-ref 机制树约简丢失信息的补偿由于这是调用图的树约简每个具体方法恰好被展开一次。树形表示天然省略了那些已经在其他分支或同一分支的先前位置探索过的方法调用——这一限制也隐式解决了递归展开问题避免无限递归。为了弥补树约简丢失的信息每个具体方法会被分配一个唯一 id方法首次到达时声明标识符idmethod-id之后再次遇到同一方法时使用标识符引用指向上一次展开的位置id-refmethod-id。每个idmethod-id与id-refmethod-id后面都带一个空格便于在文件中直接搜索定位。从实现看MethodNode在创建时分配递增的idstatic int methodId 0而 MethodNodeReference 负责输出id-ref引用树的构建采用显式工作队列ArrayDequeMethodNode完成广度优先遍历见 buildCallTree。bci调用点的字节码索引每个调用invoke都带有调用点的字节码索引bciinvoke-bci。对于内联inline方法的调用invoke-bci是一组 bci 值使用-分隔按内联位置从内向外列举直至最初的调用位置。这一链式 bci 在源码中由SourceReference[]数组与 formatLocation 实现Collectors.joining(-)其来源是每个调用位置的BytecodePosition沿 caller 链向上遍历。输出顺序的确定性保证为保证报告可复现、可 diffCallTreePrinter在遍历时会对根方法按名称排序、对每个方法的调用按 bci 与方法名排序Lambda 名称比较时剔除不确定的哈希部分具体见 ReportUtils 中的比较器。这在排查构建差异时非常有用。CSV 格式当使用CSV格式-H:PrintAnalysisCallTreeTypeCSV时会生成一组包含方法与调用关系原始数据的文件共三个call_tree_methods_*.csv——方法表包含以下列Id该方法的唯一标识符Name方法名Type声明类型Parameters以空格分隔的参数类型列表无参数时源码输出为emptyReturn返回类型Display方法限定名的缩短版本便于可视化实现中通过驼峰缩写规则压缩类型名见 display 方法Flags其他元数据如可见性修饰符p/P/d分别表示 public/private/protected、sstatic、ffinal、Ssynchronized、bbridge、vvarargs、nnative、aabstract、ysynthetic等见 flags 方法IsEntryPoint若为true表示该方法是调用图中的入口点根方法否则为false。call_tree_invokes_*.csv——调用表包含以下列Id该调用的唯一标识符MethodId调用所在方法的标识符BytecodeIndexes调用的字节码索引若方法被内联则为通过-连接的字节码索引链TargetId目标方法的 idIsDirect若为true该调用是直接调用否则为false。call_tree_targets_*.csv——目标连接表仅有两列InvokeId与TargetId将调用与其所有调用目标连接起来。注意源码中先输出 invokes、再输出 targets、最后输出 methods见 printCsvFiles。原因是遍历调用时会创建新的方法节点例如之前未访问到的抽象方法调用目标需要确保它们也能出现在 methods 文件中。图数据库的导入价值这三份 CSV 文件的目标是让原始数据能够被自定义脚本轻松处理或导入图数据库graph database。图数据库可以带来两类核心能力对调用树图进行精细的图形化可视化提供与文本格式不同的观察视角执行复杂查询例如展示导致某条代码路径被纳入调用树分析的全部树子集。这种查询能力对管理大型分析调用树至关重要。将文件导入图数据库的流程因数据库而异请遵循各图数据库提供商提供的导入说明。对象树Object Tree什么是对象树对象树是对包含在原生二进制堆中的对象的穷举展开。它通过对原生二进制堆对象图做深度优先遍历depth first walk得到使用-H:PrintImageObjectTree选项启用。其根roots要么是静态字段要么是包含内嵌常量的方法图打印的值是添加到原生二进制堆中的具体常量对象。从实现上看ObjectTreePrinter 继承自ObjectScanner通过scanBootImageHeapRoots扫描堆根使用LinkedHashMapJavaConstant, ObjectNodeBase记录常量到节点的映射最终以类型层次结构输出。生成的报告文件结构如下Heap roots ├── root root-field value: │ └── value-type idvalue-id toStringvalue-as-string fields: │ ├── field-1 valuenull │ ├── field-2 toStringfield-2-value-as-string (expansion suppressed) │ ├── field-3 value: │ │ └── field-3-value-type idfield-3-value-id toStringfield-3-value-as-string fields: │ │ └── object-tree-rooted-at-field-3 │ ├── array-field-4 value: │ │ └── array-field-4-value-type idarray-field-4-value-id toStringarray-field-4-value-as-string elements (excluding null): │ │ ├── [index-i] element-index-i-value-type idelement-index-i-value-id toStringelement-index-i-value-as-string fields: │ │ │ └── object-tree-rooted-at-index-i │ │ └── [index-j] element-index-j-value-type idelement-index-j-value-id toStringelement-index-j-value-as-string elements (excluding null): │ │ └── object-tree-rooted-at-index-j │ ├── field-5 value: │ │ └── field-5-value-type id-reffield-5-value-id toStringfield-5-value-as-string │ ├── field-6 value: │ │ └── field-6-value-type idfield-6-value-id toStringfield-6-value-as-string (no fields) │ └── array-field-7 value: │ └── array-field-7-value-type idarray-field-7-id toStringarray-field-7-as-string (no elements) ├── root root-field id-refvalue-id toStringvalue-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)。与调用树类似每个常量值恰好展开一次以压缩格式值首次到达时展开并分配标识符idvalue-id从多个分支再次到达同一值时使用标识符引用指向上一次展开位置id-refvalue-id。抑制/强制展开值Suppressing Expansion of Values某些值如String、BigInteger与基本类型数组默认不展开并标记为(expansion suppressed)其余类型默认展开。从源码中的 suppressTypesDefault 可以看到完整的默认抑制类型清单java.lang.String, java.math.BigInteger, java.lang.Character$UnicodeScript, sun.misc.FDBigInteger, boolean, char, byte, int, long, double, float可以通过以下两个选项控制-H:ImageObjectTreeSuppressTypescomma-separated-patterns强制抑制默认展开类型的展开-H:ImageObjectTreeExpandTypescomma-separated-patterns强制展开默认抑制或经选项抑制的类型。当-H:ImageObjectTreeSuppressTypes与-H:ImageObjectTreeExpandTypes同时指定时-H:ImageObjectTreeExpandTypes优先。这一优先级在源码的 suppressType 方法中有明确体现先检查 expand 匹配命中即不抑制再检查 suppress 匹配最后检查默认抑制列表。同样地某些根默认完全不展开并标记(expansion suppressed)例如会打印大量字符串的java.lang.Character$UnicodeBlock.map其余根默认展开。源码中的默认抑制根清单 suppressRootsDefault 包括java.lang.Character$UnicodeBlock.map*, java.lang.Character$UnicodeScript.aliases*, java.lang.ConditionalSpecialCasing.entryTable*, java.util.Formatter.fsPattern*, java.lang.Character$UnicodeBlock.of(int), java.util.ResourceBundle.getBundle(String, Locale, ResourceBundle$Control), com.ibm.icu.impl.ICUResourceBundle.BUNDLE_CACHE*, com.ibm.icu.impl.ICUResourceBundle.GET_AVAILABLE_CACHE*, com.ibm.icu.impl.ICUResourceBundleReader.CACHE*, ibm.icu.impl.DayPeriodRules$DayPeriodRulesData*, com.ibm.icu.util.ULocale.nameCache*, com.oracle.svm.guest.staging.option.RuntimeOptionsSupportImpl.set(String, Object)对应的根控制选项为-H:ImageObjectTreeSuppressRootscomma-separated-patterns强制抑制默认展开根的展开-H:ImageObjectTreeExpandRootscomma-separated-patterns强制展开默认抑制或经选项抑制的根。同样当两者同时指定时-H:ImageObjectTreeExpandRoots优先见 suppressRoot。通配符匹配语法上述所有抑制/展开选项都接受逗号分隔的模式列表对于类型模式基于类型的完全限定名且针对常量的具体类型。数组类型只需指定元素类型即可匹配该类型所有维度的一切数组对于根模式基于上文描述过的根的字符串格式。模式支持*修饰符共五种匹配方式ends-with结尾匹配*str——精确匹配所有以str结尾的条目starts-with开头匹配str*——精确匹配所有以str开头的条目contains包含匹配*str*——精确匹配所有包含str的条目equals全等匹配str——精确匹配所有与str相等的条目all匹配全部*——匹配所有条目。这一匹配逻辑在源码的 SimpleMatcher.matches 中实现其判断顺序为以*开头且不以*结尾 → 以*结尾 → 同时以*开头和结尾 → 全等。该SimpleMatcher同时也被可达性报告的类型/字段匹配复用见下文。实战示例类型抑制/展开-H:ImageObjectTreeSuppressTypesjava.io.BufferedWriter——抑制java.io.BufferedWriter对象的展开-H:ImageObjectTreeSuppressTypesjava.io.BufferedWriter,java.io.BufferedOutputStream——抑制java.io.BufferedWriter与java.io.BufferedOutputStream对象的展开-H:ImageObjectTreeSuppressTypesjava.io.*——抑制所有java.io.*对象的展开-H:ImageObjectTreeExpandTypesjava.lang.String——强制展开java.lang.String对象-H:ImageObjectTreeExpandTypesjava.lang.String,java.math.BigInteger——强制展开java.lang.String与java.math.BigInteger对象-H:ImageObjectTreeExpandTypesjava.lang.*——强制展开所有java.lang.*对象-H:ImageObjectTreeSuppressTypesjava.io.* -H:ImageObjectTreeExpandTypesjava.io.PrintStream——抑制所有java.io.*对象的展开但不抑制java.io.PrintStream-H:ImageObjectTreeExpandTypes*——强制展开所有类型的对象包括默认抑制的类型。根抑制/展开-H:ImageObjectTreeSuppressRootsjava.nio.charset.Charset.lookup(String)——抑制java.nio.charset.Charset.lookup(String)方法图中内嵌的所有常量的展开-H:ImageObjectTreeSuppressRootsjava.util.*——抑制所有以java.util.开头的根的展开-H:ImageObjectTreeExpandRootsjava.lang.Character$UnicodeBlock.map——强制展开java.lang.Character$UnicodeBlock.map静态字段根-H:ImageObjectTreeSuppressRootsjava.util.* -H:ImageObjectTreeExpandRootsjava.util.Locale——抑制所有以java.util.开头的根的展开但不抑制java.util.Locale-H:ImageObjectTreeExpandRoots*——强制展开所有根包括默认抑制的根。可达性报告Reachability Report在诊断代码体积膨胀或安全问题时开发者常常需要知道某个代码元素类型/方法/字段为什么是可达的。可达性报告正是为此设计。共有三个选项分别针对类型、方法、字段的诊断-H:AbortOnTypeReachablepattern-H:AbortOnMethodReachablepattern-H:AbortOnFieldReachablepattern每个选项右侧指定待诊断代码元素的模式类型与字段的语法与前述抑制/展开选项相同见-H:ImageObjectTreeSuppressTypes的文档即完全限定名 *通配符匹配方法的语法与方法过滤器-Djdk.graal.MethodFilter相同。这三个选项的完整帮助文本与默认值定义在 ReachabilityTracePrinter.Options 中均为可累积的多值字符串选项支持重复指定与逗号分隔Option(help Print a trace and abort the build process if any type matching the specified pattern becomes reachable. ...) public static final HostedOptionKeyAccumulatingLocatableMultiOptionValue.Strings AbortOnTypeReachable ...; Option(help Print a trace and abort the build process if any method matching the specified pattern becomes reachable. Matching is performed as per the MethodFilter option) public static final HostedOptionKeyAccumulatingLocatableMultiOptionValue.Strings AbortOnMethodReachable ...; Option(help Print a trace and abort the build process if any field matching the specified pattern becomes reachable. ...) public static final HostedOptionKeyAccumulatingLocatableMultiOptionValue.Strings AbortOnFieldReachable ...;当某个选项被启用且对应代码元素确实可达时构建会将**可达性追踪reachability trace**输出到一份 TXT 文件中止 Native Image 构建源码中通过AnalysisError.interruptAnalysis抛出构建中断异常见 ReachabilityTracePrinter.report。追踪会区分类型被标记为已分配instantiated与仅被标记为可达reachable两种情形分别打印对应的可达性原因链type.getInstantiatedReason()/type.getReachableReason()。以下是-H:AbortOnTypeReachablejava.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.PosixNativeLibrarySupport4839bf0d: com.oracle.svm.core.posix.PosixNativeLibrarySupport4839bf0d embedded in │ │ │ org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java) │ │ │ at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified │ │ └── 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.NativeLibrarySupport6e06bbea: com.oracle.svm.core.jdk.NativeLibrarySupport6e06bbea embedded in org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java) at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified从示例可以看出追踪的语义信息非常丰富is marked as allocated被分配、implementation invoked实现被调用、is marked as in-heap进入堆、is reachable可达、scanning root ... embedded in扫描内嵌于某根、str: static root method静态根方法、intrinsified被内联为内建实现等标注可帮助你逐层追溯到最顶层的 VM 入口点或 ImageSingletons 查找等根原因。报告文件的位置与命名规则输出目录所有报告都生成在相对构建目录build directory的reports子目录中。执行native-image可执行文件时构建目录默认为工作目录可通过-H:Pathdir选项修改。源码中ReportUtils.report会调用Files.createDirectories自动创建报告目录并在标准输出打印# Printing description to: file提示见 ReportUtils.reportImpl。命名规则各类报告遵循统一的call_tree_binary_name_date_time.ext风格命名调用树TXTcall_tree_binary_name_date_time.txt调用树CSVcall_tree_*_binary_name_date_time.csv即call_tree_methods_binary_name_date_time.csv、call_tree_invokes_binary_name_date_time.csv、call_tree_targets_binary_name_date_time.csv对象树object_tree_binary_name_date_time.txt可达性报告同样位于 reports 目录命名如下类型可达性报告trace_types_binary_name_date_time.txt方法可达性报告trace_methods_binary_name_date_time.txt字段可达性报告trace_fields_binary_name_date_time.txt其中binary_name是生成二进制的名称可通过-H:Namename选项设置date_time为yyyyMMdd_HHmmss格式由 ReportUtils.getTimeStampString 使用DateTimeFormatter.ofPattern(yyyyMMdd_HHmmss)生成。CSV 报告的符号链接生成 CSV 格式调用树报告时还会同时创建指向最新 CSV 报告集的符号链接symbolic links遵循call_tree_*.csv结构即call_tree_methods.csv、call_tree_invokes.csv、call_tree_targets.csv方便脚本稳定引用最新一次构建的报告而无需解析时间戳。该逻辑在 CallTreePrinter.toCsvFile 中实现先写带时间戳的文件再删除旧链接并重建指向新文件的符号链接。调用树附带的三份附加清单值得一提的是无论 TXT 还是 CSV 格式CallTreePrinter.print在输出调用树之外还会生成三份附加清单见 CallTreePrinter.print命名同样遵循上述时间戳规则used_methods_binary_name_date_time.txt——分析中用到的全部方法清单used_classes_binary_name_date_time.txt——分析中用到的全部类清单used_packages_binary_name_date_time.txt——分析中用到的全部包清单Lambda 类会剥离其合成包名前缀后再取包名。这三份清单与调用树配合可以快速回答最终二进制里用了哪些类/方法/包这类问题。测试验证与独立分析场景仓库在substratevm/src/com.oracle.graal.pointsto.standalone.test下提供了针对报告产出的自动化测试 StandaloneAnalysisReportTest.java可作为验证与理解报告管线的参考testPrintAnalysisCallTree以ClassEqualityCase为小规模输入通过-H:PrintAnalysisCallTree与-H:StandaloneAnalysisReportsPathdir运行分析断言reports目录被创建且其中存在报告文件testPrintAnalysisObjectTree通过-H:PrintImageObjectTree验证对象树报告产出并注明该报告当前要求宿主 VMAccess因为 Espresso 外部 JVMCI 无法物化Class对象用于报告格式化。这证明在完整native-image构建之外报告机制同样支持在独立的点到点分析模式下使用适用于需要隔离分析阶段、快速迭代诊断的场景。快速上手清单看调用关系构建时加-H:PrintAnalysisCallTree产物为reports/call_tree_binary_ts.txt做机器处理/图数据库分析再加-H:PrintAnalysisCallTreeTypeCSV得到三份 CSV 与call_tree_*.csv符号链接看堆对象保留情况构建时加-H:PrintImageObjectTree产物为reports/object_tree_binary_ts.txt若输出过大用-H:ImageObjectTreeSuppressTypes.../-H:ImageObjectTreeSuppressRoots...收窄或用-H:ImageObjectTreeExpandTypes.../-H:ImageObjectTreeExpandRoots...深入特定类型追查为什么可达针对性地加-H:AbortOnTypeReachablepattern或 Method/Field 变体构建会在命中时输出reports/trace_types_binary_ts.txt或 methods/fields 对应文件并中止依据追踪链逐层溯源调整报告位置与二进制名用-H:Pathdir与-H:Namename控制reports目录位置与报告文件名中的二进制名部分。进一步阅读Command-line Options完整命令行选项参考包括-H:Path、-H:Name与-Djdk.graal.MethodFilter等本文涉及选项的详细说明Native Image BasicsNative Image 构建与配置基础Build Output构建输出的解读与诊断信息本文涉及的报告实现源码目录substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports含CallTreePrinter.java、ObjectTreePrinter.java、AnalysisReporter.java、AnalysisReportsOptions.java、ReportUtils.java可达性追踪实现见 ReachabilityTracePrinter.java。【免费下载链接】graalGraalVM compiles applications into native executables that start instantly, scale fast, and use fewer compute resources 项目地址: https://gitcode.com/gh_mirrors/gr/graal创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考