从知乎扣出来的内容 https://www.zhihu.com/question/37389356

作者:RednaxelaFX
链接:https://www.zhihu.com/question/37389356/answer/73820511
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

什么有些JVM会选择不总是做JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。

1. 编译的时间开销

解释器的执行,抽象的看是这样的:

  1. 输入的代码 -> [ 解释器 解释执行 ] -> 执行结果

而要JIT编译然后再执行的话,抽象的看则是:

  1. 输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果

说JIT比解释快,其实说的是“执行编译后的代码”比“解释器解释执行”要快,并不是说“编译”这个动作比“解释”这个动作快。

然而这JIT编译再怎么快,至少也比解释执行一次略慢一些,而要得到最后的执行结果还得再经过一个“执行编译后的代码”的过程。

所以,对“只执行一次”的代码而言,解释执行其实总是比JIT编译执行要快。
怎么算是“只执行一次的代码”呢?粗略说,下面两个条件同时满足时就是严格的“只执行一次”

  • 只被调用一次,例如类的初始化器(class initializer,<clinit>()V)
  • 没有循环

对只执行一次的代码做JIT编译再执行,可以说是得不偿失。
对只执行少量次数的代码,JIT编译带来的执行速度的提升也未必能抵消掉最初编译带来的开销。
只有对频繁执行的代码,JIT编译才能保证有正面的收益。

况且,并不是说JIT编译了的代码就一定会比解释执行快。切不可盲目认为有了JIT就可以鄙视解释器了,还是得看实现细节如何。
有个很经典的例子:LuaJIT 2里有一个实现得非常优化的解释器,它解释执行的速度甚至比LuaJIT 1的JIT编译后的代码的速度还要快。

2. 编译的空间开销

举个最简单的例子:

  1. public static int foo() {
  2. return 42;
  3. }

其字节码大小只有3字节:

  1. public static int foo();
  2. Code:
  3. stack=1, locals=0, args_size=0
  4. 0: bipush 42
  5. 2: ireturn

而由Linux/x86-64上的HotSpot VM的Server Compiler将其编译为机器码后,则膨胀到了56字节:

  1. # {method} 'foo' '()I' in 'XX'
  2. # [sp+0x20] (sp of caller)
  3. 0x00000001017b8200: sub $0x18,%rsp
  4. 0x00000001017b8207: mov %rbp,0x10(%rsp) ;*synchronization entry
  5. ; - XX::foo@-1 (line 3)
  6. 0x00000001017b820c: mov $0x2a,%eax
  7. 0x00000001017b8211: add $0x10,%rsp
  8. 0x00000001017b8215: pop %rbp
  9. 0x00000001017b8216: test %eax,-0x146021c(%rip) # 0x0000000100358000
  10. ; {poll_return}
  11. 0x00000001017b821c: retq
  12. 0x00000001017b821d: hlt
  13. 0x00000001017b821e: hlt
  14. 0x00000001017b821f: hlt
  15. [Exception Handler]
  16. [Stub Code]
  17. 0x00000001017b8220: jmpq 0x00000001017b50a0 ; {no_reloc}
  18. [Deopt Handler Code]
  19. 0x00000001017b8225: callq 0x00000001017b822a
  20. 0x00000001017b822a: subq $0x5,(%rsp)
  21. 0x00000001017b822f: jmpq 0x000000010178eb00 ; {runtime_call}
  22. 0x00000001017b8234: hlt
  23. 0x00000001017b8235: hlt
  24. 0x00000001017b8236: hlt
  25. 0x00000001017b8237: hlt

对一般的Java方法而言,编译后代码的大小相对于字节码的大小,膨胀比达到10x是很正常的。上面的例子比较极端一些,但还是很能反映现实状况的。

同上面说的时间开销一样,这里的空间开销也是,只有对执行频繁的代码才值得编译,如果把所有代码都编译则会显著增加代码所占空间,导致“代码爆炸”(code size explosion)。

3. 编译时机对优化的影响

有些JIT编译器非常简单,基本上不做啥优化,也倒也没啥影响。
但现代做优化的JIT编译器都非常注重使用profile信息,而profile是需要通过执行用户程序来获取的。
这样,编译得太早的话,就来不及收集足够profile信息,进而会影响优化的效果;而编译太迟的话,即便收集了很多高质量的profile,但却也已经付出了profile的额外开销,编译出来的代码再快或许也弥补不过来了。

在解释器里实现收集profile的功能,等解释执行一段时间后再触发JIT编译,这样就可以很好的平衡收集profile与编译优化这两方面。

当然,收集profile也可以在JIT编译器里做:一开始先JIT编译生成收集profile的版本的代码,等收集了到足够profile后触发重新编译,再生成出优化的、不带profile的版本。JRockit基本上就是这样做的。这方面在本回答开头放的链接里已有说明。

为什么 JVM 不用 JIT 全程编译的更多相关文章

  1. 为什么 JVM 不用 JIT 全程编译?

    考虑到跨平台,所以无法使用AOT: 考虑到执行效率,所以无法全部使用JIT: 编译技术大约分为两种,一种AOT,只线下(offline)就将源代码编译成目标机器码,这是普遍用在系统程序语言中:另一种是 ...

  2. JIT——即时编译的原理

     介绍 java 作为静态语言十分特殊,他需要编译,但并不是在执行之前就编译为本地机器码. 所以,在谈到 java的编译机制的时候,其实应该按时期,分为两个部分.一个是 javac指令 将java源码 ...

  3. JVM的JIT机制

    因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度.

  4. 小师妹学JVM之:JIT中的LogCompilation

    目录 简介 LogCompilation简介 LogCompilation的使用 解析LogCompilation文件 总结 简介 我们知道在JVM中为了加快编译速度,引入了JIT即时编译的功能.那么 ...

  5. How would you differentiate JDK, JRE, JVM, and JIT?

    Q5. How would you differentiate JDK, JRE, JVM, and JIT?A5. There is no better way to get the big pic ...

  6. 浅析 JIT 即时编译技术

    即时编译回顾 HotSpot 虚拟机执行 Java 程序时,先通过解释器对代码解释执行,发现某个方法或代码块执行比较频繁后,对热点代码进行编译,编译后生成与本地平台相关的机器码,再去执行机器码获得较高 ...

  7. 小师妹学JVM之:深入理解JIT和编译优化-你看不懂系列

    目录 简介 JIT编译器 Tiered Compilation分层编译 OSR(On-Stack Replacement) Deoptimization 常见的编译优化举例 Inlining内联 Br ...

  8. JVM JIT动态编译

    一.概述 1.1 基本概念 a. 动态编译(dynamic compilation)指的是"在运行时进行编译":与之相对的是事前编译(ahead-of-time compilati ...

  9. JVM之JIT

    JIT技术是JVM中最重要的核心模块之一.我的课程里本来没有计划这一篇,但因为不断有朋友问起,Java到底是怎么运行的?既然Hotspot是C++写的,那Java是不是可以说运行在C++之上呢?为了澄 ...

随机推荐

  1. 【Java集合的详细研究8】List,Set,Map用法以及区别

    Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素.一些Collection允许相同的元素而另一些不行.一些能排序而另一些不行.Java ...

  2. 对Repository模式误用的反思和纠正

    一直以来想自己做一套开发框架,在其基础上进行快速开发,自从接触微软的MVC框架和Entityframework以来,阅读了大量园子里的相关的技术文章,也进行了不少摸索和尝试,中间经历了多次大刀阔斧的重 ...

  3. DevExpress ASPxComboBox lost selected item after postback

    <dx:ASPxComboBox ID="cbxSname" ClientInstanceName="cbxSname" Font-Names=" ...

  4. Vue 相关难点汇总

    1. 父子组件的双向数据绑定,所以在子组件是不允许修改父组件的属性的. // 解决办法 // 在子组件data中定义一个父组件传递过来的副本,再把该副本利用this.$emit("" ...

  5. axure 认证授权注册码2017-12-25

    Licensee: University of Science and Technology of China (CLASSROOM)Key: DTXRAnPn1P65Rt0xB4eTQ+4bF5IU ...

  6. mysql数据库索引相关

    一 介绍 什么是索引? 索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构.索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要.索引优化应该是 ...

  7. 在C#中如何定义一个变长的结构数组?如果定义好了,如何获得当前数组的长度?

    用ArrayList,他就相当于动态数组,用add方法添加元素,remove删除元素,count计算长度

  8. Java eclipse Myeclipse tomcat安装及配置

    Java eclipse Myeclipse tomcat安装及配置作者:天涯 来源:中国自学编程网 发布日期:1223857747目前,开发Java网页程序,最流行的就是用Myeclipse来进行编 ...

  9. jqprint 打印分页

    再要分页的结尾处加上<div style="page-break-after:always;"></div> 就可以了

  10. HPU 1471:又是斐波那契数列??(大数取模)

    1471: 又是斐波那契数列?? 时间限制: 1 Sec 内存限制: 128 MB 提交: 278 解决: 27 统计 题目描述 大家都知道斐波那契数列吧?斐波那契数列的定义是这样的: f0 = 0; ...