云时代的 JVM 原理与实战
康杨
京东资深架构师
3111 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 39 讲
云时代的 JVM 原理与实战
15
15
1.0x
00:00/00:00
登录|注册

04|从JIT到AOT:JVM编译器的云原生演进之路

你好,我是康杨。今天我们来聊一聊 JVM 的编译器。
JVM 的一个重要职责就是把字节码拿到实际运行的物理机上去执行,其中重要的一环,就是根据不同的底层操作系统和 CPU 架构,把字节码转化为实际物理机能够识别的机器码。

字节码转化为机器码的发展历程

在 JVM 的演进历程中,字节码到机器码的转化环节共经历了三个发展阶段,分别是解释执行阶段、解释执行 + 编译执行阶段、提前编译阶段。

解释执行(Interpreter Execution)

解释执行就是将编译好的字节码一行一行地翻译成机器码执行。这种模式在 JVM 的早期版本中就已经存在了,它舍弃了编译时间,只在程序运行时把字节码实时翻译为机器代码。
在解释执行过程中,由于每次都需要重新解释字节码,相同的字节码会存在被反复多次翻译执行的情况,所以采用这种模式的程序运行性能一般比较低。为此,JVM 在解释执行的基础上引入了即时编译执行技术。

即时编译(Just in Time Compilation)

即时编译也就是我们常说的 JIT,以方法为单位,即时编译将字节码一次性翻译为机器码后再执行。JIT 编译器从 JDK 1.1 版本开始引入。通过这种技术,JVM 就可以发现某段字节码被反复执行,从而启动 JIT 编译器,把这段字节码编译成机器代码,提高运行速度。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

JVM编译器的演进历程经历了解释执行、即时编译和提前编译三个阶段。逃逸分析是JIT编译器的重要优化技术,通过判断对象是否逃出方法作用域来进行优化。适用场景包括局部变量的合理设计和使用,以及使用final关键字限制对象可变性等。JIT编译器的演进从C1、C2到Graal,Graal作为C2的接班者,在性能方面表现出色。另外,JVM在逐步从JIT编译向AOT编译转变,结合AOT编译可以提高程序的运行速度和启动速度,适应云原生和容器化环境。整体而言,JVM编译器的演进旨在提升字节码执行性能,逐步转变为依赖云原生时代提供的平台无关性解决方案。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《云时代的 JVM 原理与实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(13)

  • 最新
  • 精选
  • 浩仔是程序员
    AOT还可以使用反射和动态代理吗?

    作者回复: 这个问题非常好,使用AOT后无法直接使用反射等动态的特性,目前业内有两个解决方案分别是Spring Boot 3和Quarkus,他们提供了在使用AOT编译时处理反射和动态代理问题的解决方案。它们通过编译时生成配置、代码分析和优化等手段,实现了AOT编译和Java的动态特性的结合,我会在实战篇中详细介绍这部分,可以到时候关注下

    2023-08-30归属地:广东
    2
    7
  • 临风
    java首先是通过javac编译成字节码,然后jvm才能通过执行字节码执行程序。jvm有两种执行方式,解释执行和编译执行,解释执行就是jvm直接翻译字节码为机器码运行,编译执行是jvm先将字节码编译成机器码并且缓存起来再执行。 很明显解释执行在第一次绝对是比编译执行快的,但如果一段代码执行的次数多了,那么编译执行的效率反而是比解释执行高了。所以jvm会将热点代码进行编译执行,而大部分代码仍然保持解释执行。这也是为什么Java需要运行一段时间才能达到性能巅峰的原因。 java使用c1(速度快、优化差、针对简单的逻辑)、c2(速度慢、优化好、针对复杂的逻辑)来进行编译,使用C++编写的,现在已经难以维护了。所以使用java推出了新的graal编译器代替c2编译器。这些编译器都属于JIT的范畴,都是在运行时去编译代码。 为了适应云原生的背景,java推出了aot,支持直接将java文件编译为二进制执行文件,使用graal VM代替jvm执行,实现了毫秒级的启动时间。由于没有了运行时,对整个java生态也提出了挑战,不过spring boot3已经率先支持了这一特性。 以上就是对本文的小结和自己一些简单的认识,如果有问题,还望老师指正。

    作者回复: 总结的非常好👍🏻

    2023-08-29归属地:广东
    5
  • 追逐我的明天。
    关于逃逸分析 我想问个问题 作者原话:通过 JIT 我们能够确定哪些对象可以被限制在方法内部使用,不会逃逸到外部,然后可以对它们进行优化 但是下面的代码示例,引用肯定被传递到外面了,但是这段代码不还是被优化了嘛?我现在不太明白是传递到外面的 会被优化 还是不会被优化呢?

    作者回复: 通过逃逸分析,JVM可以知道一个对象的使用范围,从而确定该对象是否可以"逃逸"出方法或线程等。如果一个对象只能从创建它的线程访问,那么就可以在栈上分配它的内存,而不是在堆上。这样可以避免后续的垃圾回收。对于上面的代码,经过逃逸分析,我们可以看到 `food` 对象只在 `main` 函数中使用,它没有被返回或者没有被其他线程引用,也就是说它的"使用范围"仅限于当前线程。因此,JVM就可以直接在栈上分配 `food` 对象的内存,从而提高程序性能。这是逃逸分析的基本思想,不过在实际的JVM实现中,这种优化可能并不会发生,因为只有当对象的大小和存活时间符合一定的条件时,才会将对象的内存分配从堆转移到栈。上述代码只是一个简单的示例,实际上JVM的发现过程可能比这个要复杂得多。

    2023-08-31归属地:北京
    2
  • 学无涯
    看老师发的jit对比aot的图,好像aot除了启动时间,其他方面都不如jit,是我理解错了吗?

    编辑回复: 1. 峰值吞吐:AOT在运行时能够快速获取和执行代码,它的执行速度比JIT快。【一段时间内吞吐量的最大值,吞吐量越高,性能越好,所以图片显示AOT的条比JIT长】 2. 编译时间:因为是即时编译,所以JIT比AOT更快。 3. 包大小:因为AOT编译器需要将整个应用程序编译成可执行的二进制文件,所以AOT的包大小要比JIT大。 可能这张图片会让人产生一些误解,谢谢你的反馈,我们调整一下🌹

    2023-08-28归属地:北京
    2
  • 张申傲
    请教老师,使用 AOT 之后,一些基于字节码增强技术的框架是不是也无法支持了,比如 SkyWalking?

    作者回复: 主流的 JVM 实现,如 HotSpot,还是首先优先支持 JIT 编译,只有在特定的场景下才会考虑使用 AOT 编译,因此,SkyWalking 在大部分情况下应该是可以正常使用的。但如果在你的应用中启用了 AOT 编译,并且发现 SkyWalking 无法正确工作,那么你可能需要考虑关闭 AOT 编译,或者寻找其他的解决方案。

    2023-09-16归属地:北京
    1
  • 小飞同学
    那为啥不直接用aot直接将字节码转换为机器码?jit编译器感觉可以废弃了

    作者回复: Ahead-of-Time(AOT)编译并没有完全替代Just-in-Time(JIT)编译,而是和JIT编译结合在一起来使用。AOT编译和JIT编译各有优缺点。例如,AOT编译的优点在于它可以在应用程序运行前进行,从而减少了应用程序启动时的延迟。另一方面,它的缺点是,由于它不能利用到运行时的信息,例如运行时的类型信息和热点代码信息等,因此它往往无法达到JIT编译那样的优化程度。因此,目前的JVM实现往往结合使用AOT编译和JIT编译,以便同时利用二者的优点。一般的做法是,在应用程序启动时,先使用AOT编译生成的代码,然后在运行过程中,通过JIT编译对热点代码进行更深度的优化。这种做法既可以减少应用程序的启动延迟,又可以保证应用程序运行时的高效性能。

    2023-09-08归属地:浙江
    1
  • 小麦
    为了从 JIT 过渡到 AOT,JVM 将字节码与 AOT 编译相结合。在 JIT 编译运行时,JVM 会监视代码的执行情况并收集相关的运行时信息,然后将这些信息传递给 AOT 编译器。AOT 编译器会利用这些信息对字节码进行优化,并生成可执行的本地机器代码。这样,当相同的代码再次执行时,就可以直接使用 AOT 编译得到的机器代码,而无需再次启动 JIT 编译。 从此描述中,没看出在 JIT 编译器识别出热点代码后,交给 AOT 编译器的好处是什么

    作者回复: 这两种技术各有优势,也各有劣势。对于JIT,每次程序启动时都需要花费时间进行编译,甚至在运行时也需要不断编译新发现的热点代码。而AOT虽然可以避免运行时编译带来的延迟,但是因为无法利用运行时信息进行优化,所以其生成的代码的效率往往不如JIT。但是,如果我们能够找到一种方法,利用运行时的信息进行AOT编译,那么就可以把两者的优势结合起来。这就是JVM将字节码与AOT编译相结合的原因:它先使用JIT编译器进行编译,找出热点代码并进行优化,然后把这些信息传递给AOT编译器,让它对字节码进行进一步的优化。通过这种方式,我们既可以利用运行时的信息进行优化,获得高效的机器码,又可以避免运行时编译带来的延迟,从而实现更快的程序启动和执行速度。

    2023-09-15归属地:广东
    3
  • geektime_zpf
    “为了从 JIT 过渡到 AOT,JVM 将字节码与 AOT 编译相结合。在 JIT 编译运行时,JVM 会监视代码的执行情况并收集相关的运行时信息,然后将这些信息传递给 AOT 编译器。AOT 编译器会利用这些信息对字节码进行优化,并生成可执行的本地机器代码。”,老师好,文中这几句不理解,jit收集的运行时信息,怎样传递给aot?

    作者回复: JIT 编译器在运行时收集的信息,可以通过一些内部机制传递给 AOT 编译器,用于对字节码进行优化。具体来说,这个传递的过程可能是通过 JIT 编译器将信息存储在一些特定的数据结构中,这些数据结构可以被 AOT 编译器读取。也可能是通过 JVM 提供的一些 API,允许 AOT 编译器查询 JIT 编译器的信息。然后,AOT 编译器就可以利用这些信息进行优化了。 例如,如果 JIT 编译器收集到的信息显示某个方法被频繁调用,那么 AOT 编译器就可能会针对这个方法进行特别的优化,使其运行更快地优化方法的执行(比如,使用内联、循环展开、去除死代码等手段),并把优化后的代码缓存起来,下次直接使用。

    2023-09-06归属地:广东
  • Levi
    使用 final 关键字来限制对象的可变性,这样 JIT 编译器更容易进行逃逸分析和优化。 老师这句话不太理解,能解释一下吗请问

    作者回复: 如果在定义一个对象时,用final关键字来修饰它,那么这个对象一旦被初始化后就不能再被修改,它的内部状态是不可变的。这样,JIT编译器在进行逃逸分析时,就可以更准确地判断出哪些对象没有发生逃逸,然后对它们进行优化,提升程序的运行效率。

    2023-08-29归属地:北京
  • peter
    请教老师几个问题啊: Q1:AOT难道没有运行时吗? 本课的第二张图,就是编译的那个图,AOT只有编译时,难道没有运行时吗? Q2:AOT提前编译,编译的时候需要选择目标平台吗?比如,目标是Linux或Windows。 Q3:AOT必须与JIT结合吗?从文中看,好像AOT是基于JIT才能工作。 Q4:AOT与JIT的对比图中,吞吐量这一项,AOT的条比JIT的短, 这个正确吗?

    作者回复: A1:AOT(Ahead-of-Time Compilation)是指在运行程序之前,就已经将代码完全编译成本地机器码,所以的确主要关注的是编译时。然而,运行时的一些相关特性(比如反射,动态加载等)可能被限制或者需要一些特殊的处理。 A2:是的,AOT编译需要选择目标平台或者特定的运行环境。因为它直接将代码编译成特定平台的机器码,所以需要知道目标平台的硬件和操作系统信息。 A3:AOT并不必须和JIT结合。它们是两种截然不同的技术,各有各的优缺点。但在一些实现方案中,它们可能会结合使用,比如在Java的HotSpot虚拟机中,它支持一种混合模式,在程序启动初期使用解释执行,然后根据运行时的信息选择热点代码进行JIT或AOT编译,以提高效率。 A4:在AOT和JIT的对比中,AOT的吞吐量可能会小于JIT。因为AOT在程序运行前就已经进行了全部编译,初始启动时性能较好,但由于缺乏运行时信息,可能无法对特定的运行时行为进行优化。相反,JIT可以在运行时针对实际代码的行为进行优化,因此在运行一段时间后,其性能可能会优于AOT,吞吐量也就相应更高。同时,这一点也会受到实现方式等多种因素的影响,并不是绝对的。

    2023-08-28归属地:河南
收起评论
大纲
固定大纲
字节码转化为机器码的发展历程
解释执行(Interpreter Execution)
即时编译(Just in Time Compilation)
显示
设置
留言
13
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部