Refresh your Java skills--Java中的即时编译(Just-in-time compilation)

因本身在写的关于Java9的新书由于篇幅和读者层次的缘由并不能将能想到的东西都写进去,故接下来整理出一系列的博文来补充拓展。html

像其余一些编程语言同样,Java一般也被称为“编译语言”。但有时你可能会感到困惑,尤为是当有人告诉你Java是JIT编译,并问你其中的一些小细节时。java

本文就来讲一说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执行,有兴趣能够看看优化

第二个优势是便携性。转换为字节码的代码能够在安装了虚拟机的任何计算机上运行。spa

Java中的即时编译

So,Java是即时编译为机器代码的。想要检查编译机器代码,咱们能够启用多个JVM参数:.net

  • -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能够理解的语言(机器代码)。编译器不会进行简单的编译,由于它也对编译代码进行了一些优化。因为这些优化,机器代码尽量地适应目标机器,另外,能够根据blog.csdn.net/opensure/ar… 这篇文章中的两张图来更好的理解下上面所说的一些细节。

相关文章
相关标签/搜索