【JVM】JIT编译器

JIT 可以理解为:Java 程序刚开始是解释执行的,运行一段时间后,JVM 发现某些代码经常被执行,就把这些代码编译成本地机器码,以后直接运行机器码,提高性能。


1. 为什么 Java 需要 JIT?

Java 程序的执行流程大概是:

java 复制代码
Java 源代码
   ↓ 编译
.class 字节码
   ↓ JVM 执行
机器指令

Java 编译器 javac 编译出来的不是某个操作系统专用的机器码,而是 字节码

所以 Java 能跨平台:

text 复制代码
同一份 .class 字节码
Windows JVM 可以执行
Linux JVM 可以执行
Mac JVM 可以执行

但是问题来了:

字节码不能直接被 CPU 执行,CPU 只能执行本地机器码。

所以 JVM 有两种方式处理字节码:

text 复制代码
方式一:解释器逐行解释执行
方式二:JIT 编译器把热点代码编译成本地机器码执行

2. 解释器是什么?

解释器的作用是:

text 复制代码
读一行字节码
翻译成机器指令
执行
再读下一行
再翻译
再执行

优点是启动快、灵活。

缺点是:同一段代码每次执行都要重新解释,效率较低。

比如:

java 复制代码
for (int i = 0; i < 1000000; i++) {
    add(i);
}

如果 add(i) 每次都靠解释器解释执行,那会很慢。


3. JIT 是什么?

JIT 全称是:

text 复制代码
Just-In-Time Compiler
即时编译器

它的作用是:

text 复制代码
把经常执行的字节码,编译成本地机器码

也就是说,JIT 不会一开始就把所有代码都编译成本地机器码,而是等程序运行起来后,观察哪些代码经常被执行。

这些经常被执行的代码就叫:

text 复制代码
热点代码 HotSpot Code

比如:

java 复制代码
public int add(int a, int b) {
    return a + b;
}

如果这个方法被调用了很多次,JVM 会认为它是热点代码,然后 JIT 会把它编译成本地机器码。

以后再执行 add(),就不用解释器一行一行解释了,而是直接执行机器码。


4. JIT 的执行过程

可以理解成这几个步骤:

text 复制代码
1. Java 类被加载到 JVM 中
2. JVM 先用解释器执行字节码
3. JVM 统计哪些方法、哪些循环执行次数很多
4. 发现热点代码
5. JIT 把热点代码编译成本地机器码
6. 后续直接执行机器码

用一句话总结:

text 复制代码
先解释执行,发现热点后编译执行。

5. 举个简单例子

假设有代码:

java 复制代码
public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            sum(i, i + 1);
        }
    }

    public static int sum(int a, int b) {
        return a + b;
    }
}

刚开始执行时:

text 复制代码
解释器解释执行 sum 方法

执行很多次后,JVM 发现:

text 复制代码
sum 方法被调用太频繁了,是热点代码

于是 JIT 介入:

text 复制代码
把 sum 方法编译成本地机器码

后面再调用 sum()

text 复制代码
直接执行机器码,不再解释

所以性能会越来越好。

这也是为什么有些 Java 程序会有一个现象:

text 复制代码
刚启动时慢一点,运行一段时间后变快

因为刚开始主要靠解释器,后面热点代码被 JIT 编译优化了。


6. JIT 和解释器的关系

它们不是互相替代,而是配合工作。

text 复制代码
解释器:
启动快,适合执行一次性代码

JIT:
运行后优化,适合执行热点代码

可以这么理解:

text 复制代码
解释器负责"先跑起来"
JIT 负责"跑得更快"

所以 JVM 的执行模式大概是:

text 复制代码
普通代码:解释执行
热点代码:JIT 编译后执行

7. JIT 为什么能优化?

因为 JIT 是在程序运行时工作的,它可以知道很多运行时信息。

比如它可以知道:

text 复制代码
哪个方法经常被调用
哪个循环执行次数很多
哪个对象没有逃逸出方法
哪个分支几乎不会走

然后它可以针对实际运行情况做优化。


8. 常见优化技术解释

方法内联

比如原代码:

java 复制代码
int result = add(1, 2);

public int add(int a, int b) {
    return a + b;
}

方法调用本身有开销,JIT 可能会优化成:

java 复制代码
int result = 1 + 2;

这样就省去了方法调用的过程。

这就是方法内联。


常量折叠

比如:

java 复制代码
int a = 10 * 20;

JIT 可以直接优化成:

java 复制代码
int a = 200;

这样运行时就不用再计算 10 * 20


死代码消除

比如:

java 复制代码
if (false) {
    System.out.println("不会执行");
}

这段代码永远不会执行,JIT 可以直接删掉。


循环优化

比如:

java 复制代码
for (int i = 0; i < 1000000; i++) {
    // 重复操作
}

JIT 可能会对循环做展开、减少重复判断、减少无用计算等优化。

目的就是:

text 复制代码
让循环执行得更快

逃逸分析

比如:

java 复制代码
public void test() {
    User user = new User();
    user.name = "Tom";
}

如果 user 只在 test() 方法内部使用,没有返回出去,也没有被其他线程访问,那么它就是没有逃逸。

JIT 可能会优化:

text 复制代码
这个对象不一定真的分配到堆上
可能直接做栈上分配,甚至直接消除对象分配

好处是:

text 复制代码
减少堆内存使用
减少 GC 压力

9. JIT 的优点

第一,性能高

热点代码编译成本地机器码后,执行速度会比解释执行快很多。


第二,可以动态优化

JIT 是运行时优化,所以它能根据真实运行情况做优化。

比如某个方法理论上有很多分支,但实际运行中经常只走一个分支,JIT 就可以针对这个情况优化。


第三,兼顾跨平台和性能

Java 先编译成字节码,所以跨平台。

然后 JVM 在不同平台上用 JIT 把字节码编译成本地机器码,所以性能也不错。

也就是:

text 复制代码
字节码保证跨平台
JIT 保证运行性能

10. JIT 的缺点

第一,首次运行可能慢

因为代码刚开始还没被 JIT 编译,还是解释执行。

所以 Java 程序刚启动时可能没有那么快。


第二,占用额外内存

JIT 编译出来的机器码需要缓存起来,所以会占用额外内存。

这块内存通常和 JVM 的代码缓存有关,比如:

text 复制代码
Code Cache

第三,编译本身也有成本

JIT 编译代码也需要 CPU 和时间。

所以 JVM 不会把所有代码都编译,而是只编译热点代码。

否则会浪费资源。


11. 面试回答版

可以这样说:

JIT 是 JVM 中的即时编译器。Java 程序运行时,JVM 一开始通常通过解释器解释执行字节码。当某些方法或循环被频繁执行时,JVM 会把它们识别为热点代码,然后由 JIT 编译器将这些字节码编译成本地机器码,并缓存起来。之后再次执行这些代码时,就可以直接执行机器码,而不需要重复解释,从而提升性能。

JIT 的优势是可以提高热点代码的执行效率,并且可以根据运行时信息做动态优化,比如方法内联、循环优化、逃逸分析、常量折叠、死代码消除等。缺点是程序刚启动时性能可能较低,JIT 编译和机器码缓存也会带来额外的 CPU 和内存开销。

一句话总结:

text 复制代码
JIT = 运行时把热点字节码编译成本地机器码,提高 Java 程序性能。
相关推荐
苏克贝塔6 小时前
.NET开发之.net framework对比.net core
jvm
cfm_29147 小时前
JVM垃圾收集算法与收集器深度解析
jvm·测试工具·算法·性能优化
自律懒人9 小时前
AI Agent 工作流编排实战:从单 Agent 到多 Agent,手搭一套能跑通的协作系统
jvm
石一峰69911 小时前
SQLite 与 db_manager 集成关键概念详解
jvm·数据库·sqlite
布朗克1681 天前
34 JVM深入理解
java·jvm
eggrall1 天前
Linux线程:并发编程的双刃剑
jvm
程序员晨曦1 天前
深入浅出JVM内存结构
jvm·面试·职场和发展
cfm_29141 天前
JVM对象创建与内存分配机制深度解析
jvm
wuminyu1 天前
Java锁膨胀机制之偏向锁到轻量级锁源码剖析
java·linux·c语言·jvm·c++
cfm_29141 天前
JVM内存模型深度剖析与性能优化
jvm·性能优化