JVM 执行引擎:从“能跑”到“跑得快”

一、两次编译,不是绕远路

Java 程序天然经历两次编译:

  1. 前端编译
    javac

    Java 源码 → .class 字节码

  2. 后端编译

    JVM 执行引擎

    字节码 → 本地机器指令

前端编译解决"正确 "。

后端编译解决""。

这不是历史包袱,是设计选择。


二、前端编译:忠于逻辑,克制优化

javac 做优化,但很保守。

1. 死代码消除

java 复制代码
if (false) {
    System.out.println("never");
}

字节码里消失。

这是确定性优化

2. try-catch-finally 的真实面目

java 复制代码
try {
    return 1;
} catch (Exception e) {
    return 2;
} finally {
    return 3;
}

字节码结果:

  • try 的 return 被抹掉
  • catch 的 return 被抹掉
  • 只保留 finally 的 return

实现方式很"笨":
复制 finally 三次

  • try 后一份
  • catch 后一份
  • 方法末尾一份

目的只有一个:finally 一定执行

3. 为什么不做更多优化?

java 复制代码
Object obj = null;
if (obj != null) {
    // 永远走不到
}

javac 看得出,人也看得出。

它还是保留。

原因很简单:

JVM 不是 Java 专用的。

Scala、Kotlin、Groovy、Clojure

它们只要能生成合法 .class,就能跑。

如果前端编译器过度聪明:

  • 其他语言的编译器成本爆炸
  • JVM 生态直接坍塌

结论

前端编译优先保证语义,不抢性能的活。


三、解释执行:最原始的 JVM

最早的 JVM 很直接:

来一条字节码

翻译一条机器指令

执行

下一条

优点:

  • 简单
  • 启动快
  • 占用少

缺点:

就像把"人山人海"翻译成:

people mountain people sea

对,但没用。


四、为什么不直接编译成机器码?

这是 C / C++ 的路线。

优点:

  • 精准

代价:

  • 不跨平台
  • 构建复杂
  • 部署成本高

Java 赌的是另一条路:

运行时知道得更多

于是,JIT 出现了。


五、即时编译(JIT):只为热点而生

核心思想很简单:

冷代码解释执行
热代码编译缓存

Code Cache

  • 字节码 → 机器码
  • 存在内存里
  • 下次直接跑

这就是 HotSpot 名字的来源。


六、混合模式,才是默认选择

JVM 的三种执行模式:

  • -Xint:纯解释
  • -Xcomp:纯编译
  • Mixed Mode(默认)

为什么不是全编译?

编译执行有成本:

  • 热点探测
  • 编译耗时
  • Code Cache 占内存
  • 需要"预热"

短命程序

  • CLI
  • 脚本
  • Serverless 冷启动

解释执行反而更快。


七、热点代码如何识别?

JVM 用两把尺子。

1. 方法调用计数器

  • 每个方法一个计数器
  • 默认阈值:10,000 次
  • 超过 → 编译

参数查看:

bash 复制代码
-XX:+PrintFlagsInitial

关键参数:

复制代码
CompileThreshold = 10000

2. 回边计数器(Back Edge)

解决这个问题:

java 复制代码
for (;;) {
    x++;
}

没有方法调用,只有循环。

字节码层面靠什么识别?

goto 指令

  • 跳回
  • 再跳回
  • 次数够了 → 编译这一段

Server 模式默认阈值 ≈ 10700


八、OSR:运行中替换

热点编译不是等方法结束。

而是:

On-Stack Replacement

正在跑的解释执行代码

无缝切到编译版本

不停机。

不重来。


九、JIT 的真正价值:激进优化

一旦进入 JIT 世界,规则变了。

常见优化包括:

  • 方法内联
  • 逃逸分析
  • 锁消除
  • 锁粗化
  • 循环展开
  • 分支预测

这些:

  • 依赖运行时信息
  • 依赖真实数据
  • 前端编译器做不了

十、为什么 Java 能追上 C++?

答案很直白:

它在你跑的时候学习你

  • 热路径
  • 实际分支
  • 真正用到的类型

不是猜,是观察。


十一、实践(不常见)

  • 短任务:别纠结 JIT
  • 长服务:关注预热
  • Benchmark:一定要热身
  • 调优前:先看 JIT 日志
bash 复制代码
-XX:+PrintCompilation
-XX:+LogCompilation

相关推荐
没有bug.的程序员7 小时前
JVM 安全与沙箱深度解析
java·jvm·安全·gc·gc调优
Ryana7 小时前
协程不是银弹:历时半年,终于搞清了每分钟120次YGC的真相
jvm·后端
CodeAmaz7 小时前
JVM双亲委派模型详解
jvm·双亲委派
小a杰.8 小时前
Flutter 图片内存优化指南(完整版)
jvm·flutter
昊虹AI笔记8 小时前
Pycharm运行时需要JVM怎么办?
jvm·ide·pycharm
大大大大物~1 天前
JVM 之 线上诊断神器Arthas实战【内部原理?常用命令?如何使用Arthas排查cpu飙高、类加载问题、死锁、慢接口等线上问题?】
jvm·oom·arthas
zfj3211 天前
排查java应用内存溢出的工具和方法
java·开发语言·jvm·内存溢出
【非典型Coder】1 天前
volatile 的顺序性和可见性原理详解
jvm