JIT 可以理解为:Java 程序刚开始是解释执行的,运行一段时间后,JVM 发现某些代码经常被执行,就把这些代码编译成本地机器码,以后直接运行机器码,提高性能。
1. 为什么 Java 需要 JIT?
Java 程序的执行流程大概是:
Java源代码 ↓ 编译.class字节码 ↓JVM执行 机器指令Java 编译器javac编译出来的不是某个操作系统专用的机器码,而是字节码。
所以 Java 能跨平台:
同一份 .class 字节码 Windows JVM 可以执行 Linux JVM 可以执行 Mac JVM 可以执行但是问题来了:
字节码不能直接被 CPU 执行,CPU 只能执行本地机器码。
所以 JVM 有两种方式处理字节码:
方式一:解释器逐行解释执行 方式二:JIT 编译器把热点代码编译成本地机器码执行2. 解释器是什么?
解释器的作用是:
读一行字节码 翻译成机器指令 执行 再读下一行 再翻译 再执行优点是启动快、灵活。
缺点是:同一段代码每次执行都要重新解释,效率较低。
比如:
for(inti=0;i<1000000;i++){add(i);}如果add(i)每次都靠解释器解释执行,那会很慢。
3. JIT 是什么?
JIT 全称是:
Just-In-Time Compiler 即时编译器它的作用是:
把经常执行的字节码,编译成本地机器码也就是说,JIT 不会一开始就把所有代码都编译成本地机器码,而是等程序运行起来后,观察哪些代码经常被执行。
这些经常被执行的代码就叫:
热点代码 HotSpot Code比如:
publicintadd(inta,intb){returna+b;}如果这个方法被调用了很多次,JVM 会认为它是热点代码,然后 JIT 会把它编译成本地机器码。
以后再执行add(),就不用解释器一行一行解释了,而是直接执行机器码。
4. JIT 的执行过程
可以理解成这几个步骤:
1. Java 类被加载到 JVM 中 2. JVM 先用解释器执行字节码 3. JVM 统计哪些方法、哪些循环执行次数很多 4. 发现热点代码 5. JIT 把热点代码编译成本地机器码 6. 后续直接执行机器码用一句话总结:
先解释执行,发现热点后编译执行。5. 举个简单例子
假设有代码:
publicclassTest{publicstaticvoidmain(String[]args){for(inti=0;i<1000000;i++){sum(i,i+1);}}publicstaticintsum(inta,intb){returna+b;}}刚开始执行时:
解释器解释执行 sum 方法执行很多次后,JVM 发现:
sum 方法被调用太频繁了,是热点代码于是 JIT 介入:
把 sum 方法编译成本地机器码后面再调用sum():
直接执行机器码,不再解释所以性能会越来越好。
这也是为什么有些 Java 程序会有一个现象:
刚启动时慢一点,运行一段时间后变快因为刚开始主要靠解释器,后面热点代码被 JIT 编译优化了。
6. JIT 和解释器的关系
它们不是互相替代,而是配合工作。
解释器: 启动快,适合执行一次性代码 JIT: 运行后优化,适合执行热点代码可以这么理解:
解释器负责“先跑起来” JIT 负责“跑得更快”所以 JVM 的执行模式大概是:
普通代码:解释执行 热点代码:JIT 编译后执行7. JIT 为什么能优化?
因为 JIT 是在程序运行时工作的,它可以知道很多运行时信息。
比如它可以知道:
哪个方法经常被调用 哪个循环执行次数很多 哪个对象没有逃逸出方法 哪个分支几乎不会走然后它可以针对实际运行情况做优化。
8. 常见优化技术解释
方法内联
比如原代码:
intresult=add(1,2);publicintadd(inta,intb){returna+b;}方法调用本身有开销,JIT 可能会优化成:
intresult=1+2;这样就省去了方法调用的过程。
这就是方法内联。
常量折叠
比如:
inta=10*20;JIT 可以直接优化成:
inta=200;这样运行时就不用再计算10 * 20。
死代码消除
比如:
if(false){System.out.println("不会执行");}这段代码永远不会执行,JIT 可以直接删掉。
循环优化
比如:
for(inti=0;i<1000000;i++){// 重复操作}JIT 可能会对循环做展开、减少重复判断、减少无用计算等优化。
目的就是:
让循环执行得更快逃逸分析
比如:
publicvoidtest(){Useruser=newUser();user.name="Tom";}如果user只在test()方法内部使用,没有返回出去,也没有被其他线程访问,那么它就是没有逃逸。
JIT 可能会优化:
这个对象不一定真的分配到堆上 可能直接做栈上分配,甚至直接消除对象分配好处是:
减少堆内存使用 减少 GC 压力9. JIT 的优点
第一,性能高
热点代码编译成本地机器码后,执行速度会比解释执行快很多。
第二,可以动态优化
JIT 是运行时优化,所以它能根据真实运行情况做优化。
比如某个方法理论上有很多分支,但实际运行中经常只走一个分支,JIT 就可以针对这个情况优化。
第三,兼顾跨平台和性能
Java 先编译成字节码,所以跨平台。
然后 JVM 在不同平台上用 JIT 把字节码编译成本地机器码,所以性能也不错。
也就是:
字节码保证跨平台 JIT 保证运行性能10. JIT 的缺点
第一,首次运行可能慢
因为代码刚开始还没被 JIT 编译,还是解释执行。
所以 Java 程序刚启动时可能没有那么快。
第二,占用额外内存
JIT 编译出来的机器码需要缓存起来,所以会占用额外内存。
这块内存通常和 JVM 的代码缓存有关,比如:
Code Cache第三,编译本身也有成本
JIT 编译代码也需要 CPU 和时间。
所以 JVM 不会把所有代码都编译,而是只编译热点代码。
否则会浪费资源。
11. 面试回答版
可以这样说:
JIT 是 JVM 中的即时编译器。Java 程序运行时,JVM 一开始通常通过解释器解释执行字节码。当某些方法或循环被频繁执行时,JVM 会把它们识别为热点代码,然后由 JIT 编译器将这些字节码编译成本地机器码,并缓存起来。之后再次执行这些代码时,就可以直接执行机器码,而不需要重复解释,从而提升性能。
JIT 的优势是可以提高热点代码的执行效率,并且可以根据运行时信息做动态优化,比如方法内联、循环优化、逃逸分析、常量折叠、死代码消除等。缺点是程序刚启动时性能可能较低,JIT 编译和机器码缓存也会带来额外的 CPU 和内存开销。
一句话总结:
JIT = 运行时把热点字节码编译成本地机器码,提高 Java 程序性能。