JIT VS AOT

article/2025/10/4 2:06:40

一、AOT,JIT是什么

JIT,即Just-in-time,动态(即时)编译,边运行边编译;AOT,Ahead Of Time,指运行前编译,是两种程序的编译方式

  1. 理解 jit、aot

程序主要有两种运行方式:静态编译与动态解释。静态编译的程序在执行前全部被翻译为机器码,通常将这种类型称为AOT (Ahead of time)即 “提前编译”;而解释执行的则是一句一句边翻译边运行,通常将这种类型称为JIT(Just-in-time)即“即时编译”AOT程序的典型代表是用C/C++开发的应用,它们必须在执行前编译成机器码,而JIT的代表则非常多,如JavaScript、Python等,事实上,所有脚本语言都支持JIT模式。但需要注意的是JIT和AOT指的是程序运行方式,和编程语言并非强关联的,有些语言既可以以JIT方式运行也可以以AOT方式运行,如Java、Python,它们可以在第一次执行时编译成中间字节码、然后在之后执行时可以直接执行字节码,也许有人会说,中间字节码并非机器码,在程序执行时仍然需要动态将字节码转为机器码,是的,这没有错,不过通常我们区分是否为AOT的标准就是看代码在执行之前是否需要编译,只要需要编译,无论其编译产物是字节码还是机器码,都属于AOT。

2. 区别

这两种编译方式的主要区别在于是否在“运行时”进行编译

  1. 优劣
  • JIT优点:
  • 可以根据当前硬件情况实时编译生成最优机器指令(ps. AOT也可以做到,在用户使用是使用字节码根据机器情况在做一次编译)
  • 可以根据当前程序的运行情况生成最优的机器指令序列
  • 当程序需要支持动态链接时,只能使用JIT
  • 可以根据进程中内存的实际情况调整代码,使内存能够更充分的利用
- JIT缺点:
  • 编译需要占用运行时资源,会导致进程卡顿
  • 由于编译时间需要占用运行时间,对于某些代码的编译优化不能完全支持,需要在程序流畅和编译时间之间做权衡
  • 在编译准备和识别频繁使用的方法需要占用时间,使得初始编译不能达到最高性能
- AOT优点:
  • 在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗
  • 可以在程序运行初期就达到最高性能
  • 可以显著的加快程序的启动
- AOT缺点:
  • 在程序运行前编译会使程序安装的时间增加
  • 牺牲Java的一致性
  • 将提前编译的内容保存会占用更多的外存

二、JIT深度解析

在从事Java开始的一段时间,那时候经常可以听到什么C++的瞧不起写Java的,在一些群里也经常看到二个派的人经常互怼。Java能够这么流行与它的跨平台,语言无关性是分不开的,不管你是用Java,python还是Go,只要变成对应的标准字节码文件,那么JVM都是可以识别并执行的,但是那时候的Java之所以被C++吐槽主要还是因为Java 慢,为什么这么说呢。我们写的程序虽然能被JVM识别,但是不能被机器识别,程序要运行起来,还是得让机器能够识别你的程序,所以JVM还需要一个 解释器,这个解释器就是将你的程序转换成机器能识别的指令,然后执行,如下图

在这里插入图片描述

对于一个长期运行的Java进程来说,每次执行都要经过 解释器 将程序翻译成机器指令去执行,那么这个效率就不是很好,这也是为什么Java被吐槽慢的缘故,所以为了解决这个问题,才出现了 JIT。对于一些热点代码(经常被执行的,for循环)的一些代码,在运行时,JVM会将这些代码编译成机器可以执行的机器码,并缓存起来,这样下次执行这些代码的时候,就不需要再经过 解释器去编译了,机器可以直接运行这段程序,提高性能,这个就被称为 即时编译器,简称 JIT编译器。

  1. JIT种类

在这里插入图片描述

在JDK1.8中HotSpot虚拟机中,内置了二个JIT,分别为C1编译器和C2编译器C1编译器:是一个简单快速的编译器,主要关注点在于局部性的优化,适用于执行时间较短或者对启动性能有要求的程序,C1编译器几乎不会对代码进行优化。C2编译器:是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序,根据各自的适配性,这种即时编译也被称为Server Compiler,但是由于C2代码超级复杂,无人维护,所以才会开发Java编写的Graal编译器代替C2

  1. 热点代码

JIT会将一些热点代码编译成机器能够识别的机器码然后缓存起来,比如一些经常被调用的代码,还有for循环中的代码,那么JIT如何识别出哪些是热点代码呢??为什么说是一些热点代码缓存起来,而不是全部呢?因为缓存是需要空间存储的。JVM提供了一个参数 -XX:ReservedCodeCacheSize 来限制该缓存的大小,如果空间满了,JIT就无法继续编译,编译执行就会变成解释执行,程序也就会通过解释器去执行。

java -XX:+PrintFlagsFinal -version | grep ReservedCodeCacheSize

在这里插入图片描述

  1. 热点探测

热点探测也就是检查出那些热点代码,然后进行编译,热点探测是基于计数器的热点探测,也就是会统计每个方法被调用的次数,当次数达到一个阈值的时候,就会被认为是热点代码虚拟机为每个方法准备了俩种计数器方法调用计数器回边计数器,在确定JVM的运行参数之后,这二个计数器都会有各自的一个阈值,达到阈值就会出发JIT编译。

  • 方法调用计数器:用于统计方法被调用的次数,客户端模式下默认是1500次,在服务端模式下默认是10000次(默认我们都是用服务端模式),我们可以使用以下命令查看
java -XX:+PrintFlagsFinal -version|grep CompileThreshold
  • 回边计数器:用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为 回边,该值在服务端模式下默认是10700次
在JVM内存结构中是有一个程序计数器的,它表示的是字节码需要你执行的下一行指令的行号,当我们执行
第一次循环之后,程序又回调到for循环的第一行执行,这个就叫回边for(int i = 0; i < 10000; i ++) {int a = i;int b = a + i;
}
  1. JIT是如何优化Java性能的
  • 方法内联

方法内联的优化是指将被调用方法的代码复制到发起调用方法中,避免发生真实的方法调用,举个例子

方法add1()要计算四个数加起来的和,然后又调用了 add2()方法,其实根本没必要有 add2()这个方法,所以JIT会进行优化
public int add1(int a,int b,int c,int d) {return add2(a,b) + add2(c,d);
}
public int add2(int a,int b) {return a + b;
}---------------------------------------------------------------------------------------------------------------------
优化后就一个方法就行了,也就是把 add2()方法要执行的代码直接复制到 add1()方法中执行,就不要去,调用 add2()这个方法了,这样就不存在方法调用,就一个方法就可以了
public int add1(int a,int b,int c,int d) {return a + b + c + d;
}

为什么方法内联可以优化Java性能呢?我们知道一个方法的执行在JVM内存结构中虚拟机栈对应的就是入栈,方法结束就对应着出栈,出栈和入栈都是有性能消耗的,所以少一个方法执行就减少了一次对应的出栈和入栈,性能也就能够提升。

  • 锁消除

在非线程安全情况下,我们都会使用线程安全的容器,举个例子,比如字符串拼接的StringBuffer和StringBuilder,StringBuffer的方法被关键字synchornized修饰,所以性能会比StringBuilder差,但是在局部方法中二者的性能确实差不多的,因为在局部方法中是单线程访问的,不存在线程安全问题。

// jdk8默认情况下开启了锁消除
public static void main(String[] args) {long start_sb = System.currentTimeMillis();for(int i = 0; i < 10000000; i ++) {SBuilder("king","coco");}long end_sb = System.currentTimeMillis();System.out.println("StringBuilder话费的时间:" + (end_sb - start_sb));long start_sf = System.currentTimeMillis();for(int i = 0; i < 10000000; i ++) {SBuffer("king","coco");}long end_sf = System.currentTimeMillis();System.out.println("StringBuffer话费的时间:" + (end_sf - start_sf));
}public static void SBuilder(String str1,String str2) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append(str1);stringBuilder.append(str2);
}public static void SBuffer(String str1,String str2) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append(str1);stringBuffer.append(str2);
}

花费的时间差不多
在这里插入图片描述

然后我们可以通过启动参数 -XX:-EliminateLocks 关闭锁消除,-XX:+EliminateLocks开启锁消除

在这里插入图片描述

在这里插入图片描述

闭了锁消除之后,StringBuffer所话费的时间明显增加了很多,性能降低了,JIT在编译的时候发现如果使用了线程安全的容器,比如StringBUffer,但是发现程序不会存在线程并发问题,就会执行锁消除来提高程序的性能

  • 逃逸分析

Java创建的大部分对象都是在堆中的,而不是全部的对象。逃逸分析技术就是在创建对象的时候判断你这个对象是在保存在堆中还是保存在栈中,那么保存在栈中有什么好处呢??首先说说保存在堆中吧,JVM垃圾收集的主要对象就是堆,创建的对象在堆中保存,那么当你这个对象不用 的时候就要被回收,我们知道垃圾回收是会消耗一定的性能的,但是如果你这个对象经过逃逸分析之后,发现这个对象可以在栈中分配,那么当你这个方法结束之后,也就是出栈,那么该对象自然就没了,也不需要垃圾收集器回收,这样就减少了垃圾收集器的工作,性能自然就能提升了。

我们创建了一个StringBuilder对象,但是这个对象只有在这个方法内部有效,该对象没有被返回出去,也就是说没有方法需要a()方法创建的这个StringBuilder对象,所以这个StringBuilder对象不会发生逃逸
public String a() {StringBuilder sb = new StringBuilder();sb.append("123");return "123";
}这个StringBuilder对象就发生逃逸了,因为有其它方法需要b()方法创建的StringBuilder对象,也就是说这个对象发生了逃逸
public StringBuilder b() {StringBuilder sb = new StringBuilder();sb.append("123");return sb;
}

逃逸分析性能测试:逃逸分析默认开启

public static void main(String[] args) {long start = System.currentTimeMillis();for(int i = 0; i < 50000000; i ++) {createPeople();}long end = System.currentTimeMillis();System.out.println("花费的时间是:" + (end - start));
}public static void createPeople() {People people = new People(10,"coco");
}static class People{Integer age;String name;public People(Integer age,String name) {this.age = age;this.name = name;}
}

添加打印GC收集信息
在这里插入图片描述
发现这么多对象很快就创建好了,并且没有垃圾收集日志的打印
在这里插入图片描述

  • 关闭逃逸分析

在这里插入图片描述
关闭逃逸分析之后,所话费的时间显著上升

在这里插入图片描述
并且我们可以加伤GC打印日志

在这里插入图片描述

发现有GC的收集信息,因为对象都在堆中,所以才发生了GC,相反,开启逃逸分析技术的对象是随着栈的出入直接销毁的,不需要进行GC,所以性能会提升
在这里插入图片描述

三、AOT深度解析

从java9开始,java就引入了aot(Ahead Of Time)的编译技术。

参考文章
参考文章
参考视频


http://chatgpt.dhexx.cn/article/TaIYnaIt.shtml

相关文章

对比JIT和AOT,各自的优点与缺点

编译器和解释器是什么 编译器和解释器的区别在于是否编译和执行过程是否是同时进行。 编译器所干的事&#xff0c;将一门语言 X 编译为另一门语言 Y &#xff08;可以是语言 X、高级语言、低级语言等&#xff09;&#xff0c;整个编译过程涉及词法分析、语法分析、语义分析。…

AOT(超前编译)实例分析

文章目录 一、背景二、具体实施2.1 tfcomfile 是什么&#xff1f;2.2 tfcompile 的功能是什么&#xff1f;2.3使用tfcompile 三、总结 一、背景 前边已经了解了JIT和AOT的基本概念&#xff0c;AOT(提前编译)方式就是在代码执行阶段之前全部编译成目标指令&#xff0c;进入执行…

Java在云原生的破局利器——AOT(JIT与AOT)

导读 JIT&#xff08;Just-in-Time&#xff0c;实时编译&#xff09;一直是Java语言的灵魂特性之一&#xff0c;与之相对的AOT&#xff08;Ahead-of-Time&#xff0c;预编译&#xff09;方式&#xff0c;似乎长久以来和Java语言都没有什么太大的关系。但是近年来随着Serverles…

AOT概述

11.1、AOT概述 11.1.1、JIT与AOT的区别 JIT和AOT 这个名词是指两种不同的编译方式&#xff0c;这两种编译方式的主要区别在于是否在“运行时”进行编译 &#xff08;1&#xff09;JIT&#xff0c; Just-in-time,动态(即时)编译&#xff0c;边运行边编译&#xff1b; 在程序…

Python interpreter state is not initialized. The process may be terminated.

Error occurred when finalizing GeneratorDataset iterator: Failed precondition: Python interpreter state is not initialized. The process may be terminated. [[{{node PyFunc}}]] 当我在使用TensorFlow训练时&#xff0c;出现以上错误。 我也曾以为是&#xff…

subprocess.CalledProcessError: Command ‘[where, cl]‘ returned non-zero exit status 1

当运行程序时&#xff0c;程序报错最后一行错误输出为 subprocess.CalledProcessError: Command [where, c1] returned non-zero exit status 1时&#xff0c;我们把输出得错误往前翻看&#xff0c;如果&#xff0c;报错得第一行 是UserWarning: Error checking compiler vers…

Problem Solving Process of The terminal process terminated with exit code 1

参考前辈的配置VScode C/C环境的经验&#xff1a; 成成赐我力量 bat67 参考之后我的配置 c_cpp_properties.json "configurations": [{"name": "Mac","includePath": ["/usr/include"],"browse" : {"limitS…

compilation terminated.

wqlubuntu:~/Linux/COURSE$ gcc -o fork.c test.c gcc: error: test.c: No such file or directory gcc: fatal error: no input files compilation terminated.今天在编译一个文件时&#xff0c;发现了以上很诡异的情况&#xff08;貌似我平时也是这样编译的啊&#xff0c;&am…

Try to run this command from the system terminal. Make sure that you use the问题,亲测已解决

今天使用python的时候&#xff0c;想要导包出现了这种问题 直接进入主题&#xff0c; 个人觉得是因为python和Pip的版本不匹配&#xff0c;我是使用的python3.7然后pip使用的是22.4 导致报错&#xff0c;我就把pip退回一下成为20.2.4的&#xff0c;然后就成功了&#xff0c;注意…

Error: spawn cmd ENOENT at Process.ChildProcess._handle.onexit

解决方案&#xff1a;在系统变量 Path 里面加上&#xff1a;%SystemRoot%\system32&#xff0c;关掉终端&#xff0c;重新启动项目。 详细步骤&#xff1a; 1、点击自己的 此电脑 &#xff0c;右键 属性(R) &#xff1b; ​ 2、找到 高级系统设置 ; ​ 3、点击 高级 &#x…

Process terminated

idea点击maven下的install出现错误 解决方法&#xff1a;更换maven进行重新配置 1、环境变量中找到MAVEN_HOME&#xff0c;修改新Maven的存放路径 2、修改maven相关配置 方法一&#xff1a;如下图 方法二&#xff1a;File --》 settings &#xff08;快捷键&#xff1a;Ctr…

406错误

当请求的url后缀为.hml 并且传到页面的数据类型是json时&#xff0c;就会出现406&#xff0c; 解决&#xff1a; 在web.xml中添加一个mapping&#xff0c;然后访问url后缀改为.action

KBL406-ASEMI插件整流桥KBL406

编辑&#xff1a;ll KBL406-ASEMI插件整流桥KBL406 型号&#xff1a;KBL406 品牌&#xff1a;ASEMI 封装&#xff1a;KBL-4 特性&#xff1a;整流桥 正向电流&#xff1a;4A 反向耐压&#xff1a;600V 恢复时间&#xff1a;>2000ns 引脚数量&#xff1a;4 芯片个数…

记录一次生产环境偶发HTTP响应406报错问题

背景 今天在生产环境碰到了一个不算复杂&#xff0c;但是容易让人抓狂的问题。我们的一个Rest接口偶发的报406错误&#xff0c;只在生产环境中出现&#xff0c;大致估算是三千次调用中会出现十几次的这个错误&#xff0c;在测试环境一直无法复现。 首先查了下HTTP的406状态码语…

openresty线上406 Not Acceptable实战排查

因为公司需要对接平台业务&#xff0c;然后其中肯定离不开nginx来做代理转发的&#xff0c;而且我们没有http的地址&#xff0c;全是对外暴露的https的地址。今天就遇到了一些问题&#xff0c;在对接平台的时候它们调过来经过nginx总是406报错&#xff0c;今天我就带大家一起揭…

html报406错误,Ajax请求出现406的原因和解决方法

一般出现406错误有两种可能&#xff1a; 1、如果后缀是html是不能响应json数据的。需要修改后缀名。 在做伪静态化过程中&#xff0c;以.html结尾的后缀&#xff0c;做post请求时&#xff0c;不能响应json格式&#xff0c;这是spring官方做出的处理 可以加一个后缀改为.action …

SpringMVC在返回JSON数据时出现406错误解决方案

在SpringMVC框架的使用中常常会使用ResponseBody注解&#xff0c;修饰“处理器”&#xff08;Controller的方法&#xff09;&#xff0c;这样在处理器在返回完毕后&#xff0c;就不走逻辑视图&#xff0c;而是将返回的对象转成JSON字符串响应给客户端&#xff0c;但这种操作有时…

Spring MVC 406

使用Spring MVC返回 JSON 数据有时候会在页面报出以下 406 错误。具体错误信息如下&#xff1a; 最常见的问题就是缺少 Jackson 工具包&#xff0c;它的作用是把 Java 对象转换成 JSON 输入出页面。当然这是最常见的情况&#xff0c;下面我就来介绍一下项目中出现的问题。由于项…

Sense 406错误

原来Sense 0.9.0 版本不能支持elasticsearch6.x 参考{https://blog.csdn.net/xieshanwu/article/details/78667881} 使用Chrome浏览器插件sense请求时&#xff0c;报错406 查询官方文档得到说明&#xff0c;从6.0版本开始&#xff0c;本次请求必须加上正确的 Content-Type&am…