Java中的即时编译(Just-in-time compilation)
作者:知秋 原文:http://t.cn/RYLPEMc
像其他一些编程语言一样,Java通常也被称为“编译语言”。但有时你可能会感到困惑,尤其是当有人告诉你Java是JIT编译,并问你其中的一些小细节时。
本文就来说一说JIT编译的概念。在第一部分,我们将对不同类型的编译描述一番。第二部分来说说JIT编译。接下来,我们将深入一下JIT编译在Java中比较特别的地方。
编译类型
在讨论编译类型之前,我们需要了解什么是编译。这是一个将编程语言翻译成机器可理解的语言(也称为机器代码)的过程。机器语言由CPU执行的指令组成。这个语言是由0-1构成的,如在wikibooks页面上的这个片段所示:
0001 00000111
0100 00001001
0000 00011110
即时编译
同样,我们知道,Java的javac指令不会生成机器代码,而是一些名为字节码的东西。而这不仅仅是一种语言会这么做(而这也是很多现代语言所发展的一个方向)。比如ActionScript(由ActionScript Virtual Machine执行)或CIL(由C#使用并在Common Language Runtime上执行)。
在这里,在我们的括号中所说的“执行”,也就是即时编译完成(即字节码编译成目标机器可执行的机器码)。这种特殊类型的编译发生在解释给定字节码的机器上,如ActionScript虚拟机或Java虚拟机(JVM)。字节码由他们在运行时( on runtime)编译成机器码。
这种编译带来了一些好处。第一个显着的优点是可以做到根据所运行机器参数来优化编译的代码。静态编译器为目标机器进行优化并一次生成机器代码。另一方面,JIT编译器提供了一种中间代码,它被转换和优化为特定于执行机器的机器代码。关于这里有一篇解释的比较通俗的文章动态编译和静态编译及Java执行,有兴趣可以看看
第二个优点是便携性。转换为字节码的代码可以在安装了虚拟机的任何计算机上运行。
Java中的即时编译
So,Java是即时编译为机器代码的。想要检查编译机器代码,我们可以启用多个JVM参数:
- -XX:+ PrintCompilation:通过这个参数,我们可以得到方法编译结果的输出。其输出的样例:
71 1 java.lang.String :: indexOf(70 bytes)
73 2 sun.nio.cs.UTF_8 $ Encoder :: encode(361 bytes)
87 3 java.lang.String :: hashCode(55 bytes)
- 输出被格式化为列,第一列(例如71)是时间戳。第二列返回唯一的编译器任务ID(1,2,3 …)。之后我们可以看到编译的方法。在括号中指定了编译字节码的字节。我们可以看到indexOf方法的大小是70字节,encode 方法是361字节等等。
- -XX:+ UnlockDiagnosticVMOptions:一个简单的标志,JVM诊断的补充选项。
- -XX:+ PrintInlining:通过这个配置,我们可以看到编译方法的细节。内联是编译器优化编译代码重要的工作方式。请看以下方法:
public void testMethod() {
callAnotherMethod();
}
通过内联,函数 callAnotherMethod()
将被 callAnotherMethod
的内容替换。正因为如此,在运行时,机器不会从一个方法跳转到另一个方法,并能够以 内联方式
执行代码。JIT通过此操作用来避免在堆栈上放置参数的复杂情况。当我们启用此参数(+PrintInlining)并运行代码时,我们可以看到类似下面的结果:
75 1 java.lang.String :: indexOf(70 bytes)
77 2 sun.nio.cs.UTF_8 $ Encoder :: encode(361 bytes)
@ 66 java.lang.String :: indexOfSupplementary(71 bytes) too big
@ 14 java.lang.Math :: min(11 bytes)(intrinsic)
@ 139 java.lang.Character :: isSurrogate(18 bytes) never executed
89 3 java.lang.String :: hashCode(55 bytes)
让我们回到理论层面面,Java中的JIT编译(这里说是动态编译)可以是(这里可以参考一篇文章JVM即时编译(JIT),我这里用更加暴力通俗的方式说了下,能知道是个什么作用就可以):
- lazy:只有真正使用的方法(在运行时调用)才会被编译成机器代码。
- adaptive(自适应):整个程序被编译成一些脏机器代码。此代码仅针对非常常用的方法进行了优化。
已经编译的字节码存储到代码缓存中。这是一个结构,所有编译的方法。当再次调用给定方法时,它不会从头开始编译,而是从代码缓存中加载。但是,当编译器认为可以更好地优化此方法时,缓存方法可以被覆盖。在优化技术中,我们可以通过以下区分:
- 内联:在前面的描述中可以知道,可以避免方法跳跃。
- 垃圾代码(称之死代码更恰当):当某些对象存在于字节码中且不被使用时,编译器可以决定从机器代码中删除它们。
- 循环优化:编译器可以组织并优化循环执行顺序或对尾递归优化成for循环等,以此来优化CPU所执行的代码。
- 用实现方法替换接口方法:当给定接口的一个方法有且仅由一个对象实现时,编译器可以决定直接使用实现的方法,以避免在运行时绑定真正实现的方法所引起的开销。
在本文中,我们解释了即时编译,即特定用于语言的编译代码(如Java的字节码)转换为CPU可以理解的语言(机器代码)。编译器不会进行简单的编译,因为它也对编译代码进行了一些优化。由于这些优化,机器代码尽可能地适应目标机器,另外,可以根据http://blog.csdn.net/opensure/article/details/46715675这篇文章中的两张图来更好的理解下上面所说的一些细节。
- Selenium2+python自动化35-获取元素属性
- 2016广东工业大学新生杯决赛网络同步赛暨全国新生邀请赛 题解&源码
- 深入浅出MongoDB复制
- Selenium2+python自动化34-获取输入框联想词
- 分解质因子(个人模版)
- 高斯混合聚类(GMM)及代码实现
- 预处理素数(个人模版)
- Cnm%(个人模版)
- Selenium2+python自动化33-文件上传(send_keys)
- hive的partition的作用和使用方法
- 线段树,最大值查询位子(个人模版)
- set使用实例1+lower_bound(val)(个人模版)
- Selenium2+python自动化29-js处理多窗口
- 谷歌「机弦」有何玄机?
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- pytest文档45-allure添加环境配置(environment)
- Pytest fixture参数化params
- Python 批量合并 Excel
- 用 Python 了解一下最炫国漫《雾山五行》
- Python 基础(六):列表与元组
- Word 批量转 PDF
- Python 数据分析(一):NumPy 基础知识
- 每日一题 | 环形排列问题
- 每日一题 | QQ群撩妹问题
- 每日一题 | 老板出的下棋问题
- Qt音视频开发12-mpv解码播放
- 算法专题 | 10行代码实现的最短路算法——Bellman-ford与SPFA
- 每日一题 | 土豪割草问题
- 高阶面试:伯努利过程
- Python 分析电影《南方车站的聚会》