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这篇文章中的两张图来更好的理解下上面所说的一些细节。

原文发布于微信公众号 - 程序猿DD(didispace)

原文发表时间:2017-11-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏沈唁志

提高PHP编程效率的53个要点

2056
来自专栏云飞学编程

Python有哪些实用的值得收藏的代码片段

我们在写代码的时候往往会遇到各种的小问题,尤其是一些反复使用的小段代码(不是库或者函数的用法技巧什么的),这里,给大家分享下个人收藏的一些代码片段,也欢迎大家一...

931
来自专栏黑泽君的专栏

c语言基础学习11_项目实战:IDE(集成开发环境)

============================================================================= ==...

1952
来自专栏冰霜之地

深入浅出 FlatBuffers 之 Schema

FlatBuffers 是一个序列化开源库,实现了与 Protocol Buffers,Thrift,Apache Avro,SBE 和 Cap'n Proto...

3062
来自专栏鸿的学习笔记

计算机基础小整理

一、CPU 在平时写的程序可以视为数据和指令的组合体,所有的程序都是copy了一份到内存中才能运行,内存地址是指在内存中保存命令和数据的场所,通过地址来标记和指...

872
来自专栏精讲JAVA

JDK 10 的 109 项新特性

虽然感觉 JDK9 发布才仅仅几周的时间,然而,随着新的 OpenJDK 的发布节奏,JDK10 已经到达发布候选里程碑阶段。

1502
来自专栏python3

习题34:模块,类,对象

创建一个名字叫mystuff.py文件,并且在里面放了个叫做apple的函数,代码如下

954
来自专栏PHP在线

PHP 底层的运行机制与原理

原文出处: nowamagic 欢迎分享原创到伯乐头条 PHP说简单,但是要精通也不是一件简单的事。我们除了会使用之外,还得知道它底层的工作原理。 PHP是...

4087
来自专栏GreenLeaves

C# (类型、对象、线程栈和托管堆)在运行时的相互关系

  在介绍运行时的关系之前,先从一些计算机基础只是入手,如下图: ? 该图展示了已加载CLR的一个windows进程,该进程可能有多个线程,线程创建时会分配到1...

2357
来自专栏用户画像

浅析JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模...

842

扫码关注云+社区

领取腾讯云代金券