![图片[1]-18 JVM即时编译器的中间表达形式:IR与优化基础-速优课](http://www.suyouke.com/wp-content/uploads/2026/07/doubao_img_2304x1728_20260722_174942-1-1024x768.png)
即时编译器在将字节码编译为机器码的过程中,会使用中间表达形式(IR)作为中间表示。IR 是编译器优化的基础,理解 IR 的结构和优化方法,有助于深入理解 JIT 编译的原理。本文将介绍 HotSpot JVM 中使用的 IR 形式以及基于 IR 的优化技术。
一、中间表达形式概述
什么是 IR
IR(Intermediate Representation)是编译器在编译过程中使用的中间代码表示形式,介于源代码和目标代码之间:
Java 源代码 → 字节码 → IR → 优化后的 IR → 机器码
IR 的作用
- 平台无关:IR 不依赖于具体的硬件平台
- 便于优化:IR 提供统一的接口进行优化
- 代码转换:IR 可以方便地进行代码转换
IR 的类型
| IR 类型 | 说明 | 适用阶段 |
|---|---|---|
| 字节码 | Java 字节码 | 初始阶段 |
| HIR(High-level IR) | 高级中间表示 | 早期优化 |
| LIR(Low-level IR) | 低级中间表示 | 后期优化 |
| 机器码 | 最终目标代码 | 输出阶段 |
二、HotSpot 的 IR 结构
HIR(High-level IR)
HIR 是 HotSpot C2 编译器使用的高级中间表示,基于静态单赋值形式(SSA):
// 原代码
int a = x + y;
int b = a + z;
// SSA 形式
int a1 = x + y;
int b1 = a1 + z;
SSA 的特点:
- 每个变量只被赋值一次
- 便于进行数据流分析
- 简化优化算法
HIR 的节点类型
| 节点类型 | 说明 |
|---|---|
| 常量节点 | 表示常量值 |
| 参数节点 | 表示方法参数 |
| 局部变量节点 | 表示局部变量 |
| 操作节点 | 表示算术、逻辑操作 |
| 调用节点 | 表示方法调用 |
| 内存节点 | 表示内存访问 |
LIR(Low-level IR)
LIR 是接近机器码的低级中间表示,考虑了具体的硬件特性:
- 寄存器分配
- 指令选择
- 内存访问模式
三、基于 IR 的优化技术
数据流分析
数据流分析是基于 IR 的核心优化技术,包括:
- 常量传播:确定变量的常量值
- 冗余消除:消除重复计算
- 死代码消除:删除永远不会执行的代码
- 活跃变量分析:确定哪些变量在后续代码中被使用
常量传播示例
// 原代码
int x = 10;
int y = x + 5;
// IR(SSA 形式)
int x1 = 10;
int y1 = x1 + 5;
// 常量传播后
int x1 = 10;
int y1 = 10 + 5; // x1 替换为 10
// 常量折叠后
int x1 = 10;
int y1 = 15; // 10 + 5 计算为 15
冗余消除示例
// 原代码
int a = x + y;
int b = x + y;
// IR(SSA 形式)
int a1 = x1 + y1;
int b1 = x1 + y1;
// 公共子表达式消除后
int temp1 = x1 + y1;
int a1 = temp1;
int b1 = temp1;
死代码消除示例
// 原代码
if (false) {
int x = 10;
System.out.println(x);
}
// IR 分析后:条件永远为 false,删除整个 if 块
四、方法内联的 IR 实现
内联过程
- 分析调用点:确定被调用方法的类型和参数
- 复制方法体:将被调用方法的 IR 复制到调用点
- 替换参数:将实际参数替换形式参数
- 优化合并后的 IR:对合并后的 IR 进行优化
内联示例
// 原代码
public int add(int a, int b) {
return a + b;
}
public int compute(int x) {
return add(x, 1) + add(x, 2);
}
// HIR(compute 方法)
int x1 = param(0);
int temp1 = invoke(add, x1, 1);
int temp2 = invoke(add, x1, 2);
int result1 = temp1 + temp2;
return result1;
// 内联后
int x1 = param(0);
int temp1 = x1 + 1; // add 方法体内联
int temp2 = x1 + 2; // add 方法体内联
int result1 = temp1 + temp2;
return result1;
// 优化后(常量传播和公共子表达式消除)
int x1 = param(0);
int result1 = (x1 + 1) + (x1 + 2);
return result1;
内联的优化效果
内联后的代码可以进行更全面的优化:
- 常量传播
- 公共子表达式消除
- 死代码消除
- 循环优化
五、逃逸分析的 IR 实现
逃逸分析的过程
- 构建对象图:基于 IR 构建对象的使用关系图
- 分析对象作用域:确定对象是否逃逸出方法
- 应用优化:根据分析结果应用栈上分配、标量替换、锁消除
栈上分配的 IR 实现
// 原代码
public void process() {
Object obj = new Object();
// 使用 obj
}
// IR
Object obj1 = new Object();
// 使用 obj1
// 逃逸分析结果:obj1 不会逃逸出方法
// 优化后:删除对象创建,直接使用栈上变量
标量替换的 IR 实现
// 原代码
class Point {
int x;
int y;
}
public void process() {
Point p = new Point();
p.x = 1;
p.y = 2;
// 使用 p.x 和 p.y
}
// IR
Point p1 = new Point();
p1.x = 1;
p1.y = 2;
// 使用 p1.x 和 p1.y
// 逃逸分析结果:p1 不会逃逸出方法
// 标量替换后
int x1 = 1;
int y1 = 2;
// 使用 x1 和 y1
六、循环优化的 IR 实现
循环展开的 IR 实现
// 原代码
for (int i = 0; i < 100; i++) {
array[i] = i;
}
// IR(循环部分)
int i1 = 0;
loop_start:
if (i1 >= 100) goto loop_end;
array[i1] = i1;
i1 = i1 + 1;
goto loop_start;
loop_end:
// 循环展开后(展开 4 次)
int i1 = 0;
loop_start:
if (i1 >= 100) goto loop_end;
array[i1] = i1;
array[i1 + 1] = i1 + 1;
array[i1 + 2] = i1 + 2;
array[i1 + 3] = i1 + 3;
i1 = i1 + 4;
goto loop_start;
loop_end:
循环向量化的 IR 实现
// 原代码
for (int i = 0; i < n; i++) {
c[i] = a[i] + b[i];
}
// IR(循环部分)
int i1 = 0;
loop_start:
if (i1 >= n) goto loop_end;
c[i1] = a[i1] + b[i1];
i1 = i1 + 1;
goto loop_start;
loop_end:
// 向量化后(使用 SIMD 指令)
int i1 = 0;
loop_start:
if (i1 >= n) goto loop_end;
// 使用 SIMD 指令处理 8 个元素
c[i1..i1+7] = a[i1..i1+7] + b[i1..i1+7];
i1 = i1 + 8;
goto loop_start;
loop_end:
七、总结
JVM 即时编译器使用中间表达形式进行优化:
- IR 的类型:HIR(高级)和 LIR(低级)
- SSA 形式:每个变量只被赋值一次,便于优化
- 基于 IR 的优化:常量传播、冗余消除、死代码消除
- 方法内联:基于 IR 的方法体复制和参数替换
- 逃逸分析:基于 IR 的对象作用域分析
- 循环优化:基于 IR 的循环展开和向量化
理解 IR 的结构和优化方法,有助于深入理解 JIT 编译的原理,以及如何编写优化友好的代码。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END








请登录后查看评论内容