HotSpot VM JIT

9 篇文章 0 订阅

解释执行和编译执行


解释执行是计算机语言的一种执行方式。由解释器(即编译器)现场解释执行,不生成目标程序。如BASIC语言的执行方式便是解释执行,一般解释执行效率较低,低于编译执行

 

使用解释执行的程序我们一般称为解释程序。它将源语言直接作为源程序输入,解释执行解释一句后就提交计算机执行一句,并不形成目标程序。如在终端上打一条命令或语句,解释程序就立即将此语句解释成一条或几条指令并提交硬件立即执行且将执行结果反映到终端,从终端把命令打入后,就能立即得到计算结果。这种工作方式非常适合于人通过终端设备与计算机会话

 

http://baike.baidu.com/view/1182922.htm?fr=aladdin

 

编译执行是一种计算机语言的执行方式。

编译程序目标代码一次性编译成目标程序,再由机器运行目标程序。

如:PASCAL,C,C++等语言。效率高于解释执行

 

http://baike.baidu.com/view/1182926.htm

 

JVM运行架构


默认情况下JVM采用解释执行,也就是JVM负责将用户输入的.java文件转化为.class文件,而class文件中存储的就是JVM定义的字节码(bytecode),所谓的字节码是JVM定义的一套指令,和汇编的指令十分相似。而JVM的字节码是跨平台的,在不同的平台上由JVM负责将字节码解释为对应的机器码执行。而解释器有多种实现,比如传统的switch语句循环方式和HotSpotVM中的基于模板的解释器。虽然HotSpot VM的解释器显著地缩短了HotSpot VM和机器码之间的性能差距,但是在某些情况下还是不如机器码。


为了解决这些性能问题,很多虚拟机的实现都引入了编译执行。在这些虚拟机中,这个编译器叫做JIT编译器(Just InTime)。

来看看JIT的定义,

Icomputingjust-in-time compilation (JIT), also known as dynamic translation, is compilation done during execution of aprogram – at runtime – rather than prior toexecution.[1] Most often this consists oftranslation to machine code, which is then executeddirectly, but can also refer to translation to another format.

JIT compilation is a combination of the twotraditional approaches to translation to machine code – ahead of time compilation (AOT), andinterpretation – and combines someadvantages and drawbacks of both.[1] Roughly, JIT compilationcombines the speed of compiled code with the flexibility of interpretation,with the overhead of an interpreter and the additional overhead of compiling(not just interpreting). JIT compilation is a form of dynamiccompilation,and allows adaptiveoptimization such as dynamicrecompilation – thus in principle JITcompilation can yield faster execution than static compilation. Interpretationand JIT compilation are particularly suited for dynamicprogramming languages, as the runtime system can handle late-bound datatypes and enforce security guarantees.

 

在HotSpot VM中解释执行和编译执行同时存在,解释器负责主要的代码执行任务,而编译器主要负责程序的性能,利用不同的优化手段提升代码的执行效率。


需要注意的是JIT编译器并不是JVM中的标准,但是却是JAVA中不可或缺的一部分。

 

HotSpot VM JIT


-Client和-Server


在HotSpot VM JIT中有两种不同的模式,C1和C2。所谓的C1就是指-client模式,在这种模式下,JVM对应用程序所做的优化较少,因为其优化定义的阈值较高。所谓的C2就是指-server模式,JVM堆应用程序做的优化较多。前者(-client)的目标主要是为了更快的启动时间以及快速编译,不会耗费大量的时间去编译,导致应用等待响应。在Java 1.4时,Client模式全方位支持方法内联,添加了堆CHA、逆优化的支持。后者(-server)的主要目标是是性能优化达到极致,吞吐量也达到最高,总之就是想尽一切办法堆代码进行优化。也这个是因为这点,后者在空间和时间的消耗上都多于前者。


编译契机


初始的时候,所有方法都是在解释器中执行的,只有当某段代码执行的次数超过一定的限制,JIT编译器才会将其编译成机器码供JVM调用。那么JVM是如何实现计数的呢? JVM中的每个方法都有两个计数器:方法调用计数器和回边计数器。方法调用计数器主要针对整个函数的优化,每次一进入函数,这个计数器就会加一。而回边计数器主要是针对循环结构的优化,每次控制流从行号靠后的字节码跳到靠前的字节码时加一(也就是从循环尾部调转到循环条件处)。虽然这两个计数器的计数方式不相同,但是触发编译的条件是相同的。当方法调用计数器的值超过阈值CompileThreshold的时,回边计数器超过CompileThreshold * OnStackReplacePercentage / 100时,进行编译。

 

CompileThreshold可以通过-XX:CompileThreshold进行设定,在-Client模式下,该值默认为1500次,在-Server模式下,该值默认为10000次。OnStackReplacePercentage可以通过-XX:+OnStackReplacePercentage进行设定,在-Client模式下,该值默认为933,在-Server模式下,该值默认为140。

 

而所有的编译任务都采用异步模式(或者说生产者-消费者),也就是说先将编译任务推送到一个队列中,只有当编译器线程不忙的时候,才会取出一个编译任务进行执行。编译器和解释器在这个时候同时运行,解释器不会等待编译器的结束,而是会在解释器中继续执行这个方法。当编译任务完成的时候,编译代码就会和该方法关联,然后在下次调用时使用新编译生成的代码。也可以通过-Xbatch或者-XX:-BackgroundCompilation将解释器设置为阻塞模式,也就是需要等待编译器完成编译任务。

当解释器执行长期运行的Java循环时,Hotspot VM采用了一种叫做栈上替换(On Stack Replacement, OSR)的特殊编译。


OSR


Effcientin validation and dynamic replacement of executing code on-stackreplacement (OSR), is necessary to facilitate effective, aggressive,specialization of object-oriented programs that are dynamically loaded, incrementallycompiled, and garbage collected。

 

On-stackreplacement (OSR)  is a four step process.

1)The runtime extracts the execution state (currentvariable values and program counter) from a particular method.

从方法中提取出运行状态(当前的变量和程序计数器)

2)The compilation systemthen recompiles the method.

编译器重新编译该方法。

3)Next, the compilergenerates a stack activation frame and updates it with the values extractedfrom the previous version.

编译器生成一个新的栈帧(stack frame),然后将之前提取出的状态更新到新生长的栈中。

4)Finally , the systemreplaces the old activation frame with the new and restarts execution in thenew version.

将老的栈帧替换为新的栈帧,然后重新启动执行。

 

GivenOSR functionality , a dynamic compilation system can implement very aggressivespecializations based on temporary conditions. Then, when these conditions changeas a result of an external event or a runtime check, the compilation system canre-compile (and possibly re-optimize) the code and replace the currentlyexecuting version


    Stack frame: One stack frame iscreated whenever a method is executed in the JVM, and the stack frame is addedto the JVM stack of the thread. When the method is ended, the stack frame isremoved. Each stack frame has the reference for local variable array, Operandstack, and runtime constant pool of a class where the method being executedbelongs. The size of local variable array and Operand stack is determined whilecompiling. Therefore, the size of stack frame is fixed according to the method.

    Local variable array: It has an indexstarting from 0. 0 is the reference of a class instance where the methodbelongs. From 1, the parameters sent to the method are saved. After the methodparameters, the local variables of the method are saved.

    Operand stack: An actual workspace ofa method. Each method exchanges data between the Operand stack and the localvariable array, and pushes or pops other method invoke results. The necessarysize of the Operand stack space can be determined during compiling. Therefore,the size of the Operand stack can also be determined during compiling.

 

逆优化


HotSpot VM中的术语“逆优化”是指那些经过若干级内联而来的编译帧转换为等价的解释器帧的过程。如果说前面所说的编译是指将解释器帧转换为编译帧的过程,那么逆优化就是这个过程的逆过程。它可以将编译代码从多种乐观优化中回退回来。


测试


禁用编译模式与开启编译模式


测试环境:windows7 x86 jdk1.7

 

首先需要说明的是,在HotSpot中默认采用的编译和解释同时存在的模式,如果需要关闭JIT编译器,需要采用-Djava.compiler=NONE参数。

 

public class JITTest {
   public static final int MAX = Integer.MAX_VALUE;
   public static void test() {
      int i= 0;
      longstart = System.currentTimeMillis();
      while(i < MAX) {
        i++;
      }
      longend = System.currentTimeMillis();
      System.out.println((end- start) + "ms");
   }
   public static void main(String[] args) {
      JITTest.test();
   }
}
 

采用如下JVM参数运行,

 

得到的结果是,

 

17164ms

 

可以看到仅仅是将I = 0增加到Integer.MAX_VALUE就花了17s。这个效率实在是太低了,如果我们开启了JIT编译器会怎么样呢?在这之前,我们先看一个重要的VM参数:-XX:+PrintCompilation,使用这个参数我们可以看到JVM做了哪些编译优化(包括逆优化和重新优化)。

 

使用这个参数后,打印出的信息格式如下所示:

<id> <type><method name> [bci] <(# of bytes)>

id可以为以下值:

1)     编译活动的id(至少占3列)

2)     ---,表示编译的是本地方法

type可以为空或者以下的一个或者多个值:

1)     %,以栈上替换(OSR)方式编译

2)     *|n,编译的是本地方法

3)     s,编译的是同步方法

4)     !,编译的方法有异常处理器

5)     b,解释器被阻塞知道编译结束(blocking)

6)     l,编译没有做完整优化,只是第1层编译。

7)     made not entrant,逆优化方法

8)     made zombie,编译的方法不再有效

method name:

不带签名的方法名

Bci可以为以下值:

1)     @ ##,是osr编译,osr的字节码索引。

# of bytes可以为以下值:

1)     (## bytes),方法字节码的字节数。

 

介绍完了-XX:+PringCompilation后,来看看使用这个参数后,上面的测试代码会有什么提升,

采用如下JVM参数运行,

 

得到结果是,

    104    1             java.lang.String::hashCode (55bytes)
    105    2             java.lang.String::charAt (29bytes)
    106    3             java.lang.String::equals (81bytes)
    111    4             java.lang.String::length (6 bytes)
    112    5             java.lang.Object::<init> (1bytes)
    116    6            java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes)
    116    7             java.lang.String::indexOf (70bytes)
    116    8            java.lang.AbstractStringBuilder::append (29 bytes)
    117    9             java.lang.String::lastIndexOf (52bytes)
    120    1 %           JITTest::test @ 9 (50 bytes)
4ms

可以看到JIT堆JITTest::test方法做了优化,而其优化类型为%(OSR,栈上替换),开始的字节码位置是9,使用JAVAP查看字节码详情,

Javap –verbose JITTest.class                          


test函数第9行到15行的字节码是,

9: iinc          0, 1
12: iload_0
13: ldc           #8                  // int 2147483647
15: if_icmplt     9

 

iinc 0,1 表示局部变量0加1,根据局部变量表可知局部变量0为i,也就是i+1。

iload_0,表示将局部变量0加载到操作数栈中

ldc #8,表示将常量池中的#8也就是整数2147483647放置到操作数栈中

if_icmplt 9,表示操作数栈中的栈顶元素和栈顶元素下面的元素进行比较,如果栈顶元素大于栈顶元素下面的元素,则跳转到字节码9处。

可以明显看到这段字节码就是,

   while (i < MAX) {
        i++;
   }
 

JIT编译器对于循环进行了编译,同样的代码在编译后运行时间只有4ms,与之前的17s相比,性能提升了4000倍。


Client模式与Server模式


测试环境: Ubuntu x64 OpenJdk-java-7

 


1) 回边计数器

 

修改上面的代码将MAX分别设置为10000,12500,15000,然后分别在client模式和server模式下运行,

 

JVM参数分别是

-server –XX:+PrintCompilation

-client –XX:+PrintCompilation

得到的结果分别是

 

10000

12500

15000

-server

0ms

0ms

46   1%      JITTest::test @ 9 (51 bytes)

1ms

 

-client

0ms

1ms

53   1%      JITTest::test @ 9 (51 bytes)

1ms

 

可见对于回边计数器,-server和-client差不了多少

2)           方法调用计数器

 

测试代码,

public class JITTest {
   public static final int MAX = 9000;
   public static void testCall() {
      for (int i = 0; i < MAX; i++) {
        test();
      }
   }
 
   public static void test() {
      int i = 0;
      int j = 10;
      int z = (int) ((i + j) * Math.random());
   }
   public static void main(String[] args){
      testCall();
   }
}


将MAX分别设置为9000, 10000,11000,然后分别在client模式和server模式下运行,

 

-client:

     78   1            java.util.concurrent.atomic.AtomicLong::get (5 bytes)
     78   3     n       sun.misc.Unsafe::compareAndSwapLong(native)  
     78   2            java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes)
     82   4            java.util.Random::next (47 bytes)
 <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

     71   1             java.util.concurrent.atomic.AtomicLong::get(5 bytes)
     71   2            java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes)
     71   3     n       sun.misc.Unsafe::compareAndSwapLong(native)  
     76   4            java.util.Random::next (47 bytes)
     77   5             JITTest::test (16bytes)<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

     70   1            java.util.concurrent.atomic.AtomicLong::get (5 bytes)
     70   2            java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes)
     70   3     n       sun.misc.Unsafe::compareAndSwapLong(native)  
     76   4            java.util.Random::next (47 bytes)
     77   5             JITTest::test (16bytes)
     77   6            java.lang.Math::random (17 bytes)

-server:

     68   1            java.util.concurrent.atomic.AtomicLong::get (5 bytes)
     68   3     n       sun.misc.Unsafe::compareAndSwapLong(native)  
     68   2            java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes)
     71   4             java.util.Random::next (47 bytes)<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

     81   1            java.util.concurrent.atomic.AtomicLong::get (5 bytes)
     81   3     n       sun.misc.Unsafe::compareAndSwapLong(native)  
     81   2            java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes)
     84   4            java.util.Random::next (47 bytes)
     85   5             JITTest::test (16bytes)
     85   6            java.lang.Math::random (17 bytes)<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

     79   1            java.util.concurrent.atomic.AtomicLong::get (5 bytes)
     79   3     n       sun.misc.Unsafe::compareAndSwapLong(native)  
     84   2            java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes)
     86   4            java.util.Random::next (47 bytes)
     88   5             JITTest::test (16 bytes)
     93   7            java.util.Random::nextDouble (24 bytes)
     96   6            java.lang.Math::random (17 bytes)
 

似乎临界值和上面所说的client = 1500,server = 10000不一样,为什么呢?可能是由于OpenJDK中HotSpot版本问题。

还是上面的代码在Oracle jdk1.7中运行,

设置MAX分别为1400,1500,1600,在client模式下运行,

 

    131    1             java.lang.String::hashCode (55bytes)
    132    2             java.lang.String::charAt (29bytes)
    133    3             java.lang.String::equals (81bytes)
    137    4             java.lang.String::length (6 bytes)
    139    5             java.lang.Object::<init> (1bytes)
    141    6             java.lang.AbstractStringBuilder::ensureCapacityInternal(16 bytes)
    142    7             java.lang.String::indexOf (70bytes)
    142    8            java.lang.AbstractStringBuilder::append (29 bytes)
    142    9             java.lang.String::lastIndexOf (52bytes)
    147   12    n      sun.misc.Unsafe::compareAndSwapLong (0 bytes)  
    147   10            java.util.concurrent.atomic.AtomicLong::get (5 bytes)
    147   11            java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes)
    147  13            java.util.Random::next (47 bytes)
 

    145    1             java.lang.String::hashCode (55bytes)
    147    2             java.lang.String::charAt (29bytes)
    149    3             java.lang.String::equals (81bytes)
    154    4             java.lang.String::length (6 bytes)
    155    5             java.lang.Object::<init> (1bytes)
    158    6            java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes)
    159    7             java.lang.String::indexOf (70bytes)
    160    8            java.lang.AbstractStringBuilder::append (29 bytes)
    160    9             java.lang.String::lastIndexOf (52bytes)
    168   10            java.util.concurrent.atomic.AtomicLong::get (5 bytes)
    169   11            java.util.concurrent.atomic.AtomicLong::compareAndSet(13 bytes)
    169   12    n      sun.misc.Unsafe::compareAndSwapLong (0 bytes)  
    169   13             java.util.Random::next (47 bytes)
    170   14             java.lang.Math::random (17 bytes)
    170   15             java.util.Random::nextDouble (24bytes)
    170   16             JITTest::test (16 bytes)

    132    1             java.lang.String::hashCode (55bytes)
    134    2            java.lang.String::charAt (29 bytes)
    137    3             java.lang.String::equals (81bytes)
    152    4             java.lang.String::length (6 bytes)
    154    5             java.lang.Object::<init> (1bytes)
    158    6            java.lang.AbstractStringBuilder::ensureCapacityInternal(16 bytes)
    159    7             java.lang.String::indexOf (70bytes)
    160    8            java.lang.AbstractStringBuilder::append (29 bytes)
    160    9             java.lang.String::lastIndexOf (52bytes)
    166   10            java.util.concurrent.atomic.AtomicLong::get (5 bytes)
    166   11            java.util.concurrent.atomic.AtomicLong::compareAndSet (13 bytes)
    167   12    n      sun.misc.Unsafe::compareAndSwapLong (0 bytes)  
    168   13             java.util.Random::next (47 bytes)
    168   14             java.lang.Math::random (17 bytes)
    169   15             java.util.Random::nextDouble (24bytes)
    169   16             JITTest::test (16 bytes)<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

可以发现在这种情况下,client的阈值在1500左右,和上面提到的数值基本上是一致的。虽然在Oracle JDK和OpenJDK中不太一样,但是不妨碍我们理解。

 


设置-XX:CompileThreshold


测试环境:windows7 x86 jdk1.7

public class JITTest {
   public static void main(String[] args){
   }
}

 

Main函数中什么都不设置,然后比较设置-XX:CompileThreshold和不设置的差别。

不设置的时候,

 

    197    1             java.lang.String::hashCode (55bytes)
    199    2             java.lang.String::charAt (29bytes)
    201    3             java.lang.String::equals (81bytes)
    206    4             java.lang.String::length (6 bytes)
    207    5             java.lang.Object::<init> (1bytes)
    210    6            java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes)
    211    7             java.lang.String::indexOf (70bytes)
    212    8            java.lang.AbstractStringBuilder::append (29 bytes)
    212    9             java.lang.String::lastIndexOf (52bytes)

设置-XX:CompileThreshold = 400,

     99    1             java.lang.String::equals (81bytes)
    100    2             java.lang.String::hashCode (55bytes)
    101    3             java.lang.String::indexOf (70bytes)
    103    4             java.lang.String::charAt (29bytes)
    104    5             java.lang.CharacterData::of (120bytes)
    104    6            java.lang.CharacterDataLatin1::getProperties (11 bytes)
    105    7             java.lang.Character::toLowerCase(9 bytes)
    105    8            java.lang.CharacterDataLatin1::toLowerCase (39 bytes)
    107   10    n       java.lang.System::arraycopy(0 bytes)   (static)
    107    9             java.lang.String::toLowerCase (472bytes)
    109   11             java.lang.String::lastIndexOf (52bytes)
    109   12            java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes)
    109   13            java.lang.AbstractStringBuilder::append (29 bytes)
    109   14            java.nio.charset.Charset::checkName (165 bytes)
    110   15             java.lang.Object::<init> (1bytes)
    110   16             java.lang.Math::min (11 bytes)
    112   17             java.lang.String::length (6 bytes)
    114   18             java.io.Win32FileSystem::isSlash(18 bytes)
    114   19 s         java.lang.StringBuffer::append (8 bytes)
    115   20             java.lang.StringBuilder::append (8bytes)
    115   21             java.io.Win32FileSystem::normalize(231 bytes)
    117   22  !        sun.net.www.ParseUtil::decode (316 bytes)
    120   23             sun.net.www.ParseUtil::encodePath(336 bytes)

设置-XX:CompileThreshold=1000000,

什么都不输出

 

并且通过观察该值越小,编译的频率越频繁,更多的解释执行代码会变成编译执行。



参考资料:

http://en.wikipedia.org/wiki/Just-in-time_compilation

http://lurnq.com/lesson/The-Magic-of-Java-The-Bytecode-JVM-JIT/

http://www.slideshare.net/CharlesNutter/javaone-2012-jvm-jit-for-dummies

http://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/geninfo/diagnos/underst_jit.html

http://www.cubrid.org/blog/dev-platform/understanding-jvm-internals/

<<java性能优化权威指南>>

<<深入理解java虚拟机:JVM高级特性与最佳实践>>

<<java虚拟机规范(java_se_7)>>

论文:Effcient and General On-Stack Replacement for Aggressive ProgramSpecialization


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值