CFG(控制流图)

返回 JDK 工具 | → 编译与运行

Control Flow Graph(控制流图) 是编译器和 JIT 对程序控制流进行分析与优化的核心数据结构。


基本概念

CFG 是一个有向图:

  • 节点(Node)= 基本块(Basic Block):顺序执行、无分支的指令序列,只有一个入口和一个出口
  • 边(Edge):表示可能的控制转移(条件跳转、无条件跳转、异常路径等)
         ┌──────────┐
         │  Entry   │
         └────┬─────┘
              │
         ┌────▼─────┐
         │  Block A │  ← 基本块:连续无跳转指令
         │  i = 0   │
         │  i < 10? │
         └──┬────┬──┘
            │yes │no
       ┌────▼──┐ └──►┌──────────┐
       │Block B│     │  Exit    │
       │ i++   │     └──────────┘
       └────┬──┘
            │
            └──────────► Block A (回边 Back Edge)

回边(Back Edge)标志着循环的存在,是 JIT 循环优化的关键识别点。


在 JVM/JIT 中的作用

JIT 编译器(C1 / C2)在将字节码编译为机器码之前,会先构建 CFG,再在其上执行多轮优化:

字节码
  │
  ▼ 构建 CFG(基本块划分)
CFG
  │
  ├─ 支配树分析(Dominator Tree)
  ├─ 活跃变量分析(Liveness Analysis)
  ├─ 循环识别(Loop Detection)
  │
  ▼ 优化 Pass
  ├─ 死代码消除(Dead Code Elimination)
  ├─ 公共子表达式消除(CSE)
  ├─ 循环不变量外提(Loop Invariant Code Motion)
  ├─ 内联展开(Inlining)
  └─ 逃逸分析(Escape Analysis)
  │
  ▼ 寄存器分配 → 机器码

基本块划分规则

编译器按以下规则切割指令序列为基本块:

  1. 函数/方法入口是新块的起点
  2. 跳转指令(gotoiftableswitch 等)的目标是新块起点
  3. 跳转指令之后的下一条指令是新块起点
  4. 异常处理器(catch 块)的起点是新块起点

支配关系(Dominance)

若从 Entry 到达节点 B 的每条路径都经过节点 A,则称 A 支配 B(A dom B)。

支配树用于:

  • SSA 构造(静态单赋值形式,JIT 内部表示)
  • 循环检测(回边 = 从 B 到其支配者 A 的边)
  • 代码外提(将循环不变量提到循环支配节点之前)

与 javac 的关系

javac 编译器自身也在语义分析阶段构建 CFG,用于:

用途说明
明确赋值分析确认变量在使用前已被赋值(Definite Assignment)
可达性分析检测不可达代码(unreachable code)
异常检查验证 checked exception 是否被捕获或声明

调试与可视化

打印 C2 IR(Ideal Graph)

# 需要 debug 版 JVM 或 fastdebug 构建
java -XX:+PrintIdealGraph -XX:PrintIdealGraphLevel=1 MainClass

Graal 编译器可视化(推荐,生产 JDK 可用):

# 使用 GraalVM 的 Ideal Graph Visualizer
java -Dgraal.Dump=:1 -Dgraal.PrintGraph=Network MainClass

相关链接

  • JIT — JIT 编译与热点优化
  • javac — 编译阶段的 CFG 用途
  • 编译与运行 — 编译到运行完整流程
  • JVM — 执行引擎架构