前言

本文尝试用浅显的语言, 解释JIT的概念和基本原理,让读者明白JIT的运行方式和作用。最后,附上关于JIT的代码样例,帮助大家更好理解JIT。本文使用JVM虚拟机为Hotspot ,一切分析都在Hotpot上。如有不对的地方,欢迎指正。

JIT简介

JIT 是just in time 的缩写,即时编译编译器。

当JIT编译启用时, JVM读入字节码文件解释后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码。

java代码编译过程

源代码经过javac编译,转换成java字节码文件(.class文件)。之后,JVM解释器将字节码文件翻译成对应的机器指令,逐条读入,逐条解释翻译。最后,对应的OS执行机器指令。

java code 需要经过解释执行,所以它的速度必然比可执行的二进制字节码程序慢。


Created with Raphaël 2.1.0 源码 javac 是否需要JIT编译? JIT编译器 机器码 解释器 yes no


JIT编译

通常,JVM执行代码时, 它并不立即开始JIT编译。
主要有两点原因

  • 代码本身只会被执行一次,那么从效率上来讲,对它进行JIT编译就是在浪费精力。因为JIT编译相对于解释器翻译字节码开销要大的多。
  • 当代码执行的次数越多, JIT就会越了解代码结构。JIT对代码的优化会更好。

那么什么时候会对代码进行JIT编译?

一般来说,当代码被频繁调用时(通常场景,代码中的循环体),该代码块将被JIT编译器编译。

JIT的编译阈值

在JVM中有两个计数器:

  • 方法被调用的次数
  • 方法中循环被回弹执行的次数

JVM在执行java方法时,它会坚持这两个计数器总和来决定是否需要JIT编译。
*-XX:CompileThreshold=N 可以配置这个阈值。在server模式下,它的默认值是10000;在client模式下,默认值是1500。
PS:不同的模式使用的JIT编译器是不同的。在client模式下, JVM使用的是一个代号为C1的轻量级编译器,而在server模式下,使用的是代号C2相对重量级的编译器。与C1相比,C2编译的更彻底,所以服务起来后,性能更高 *
2

JIT的优化策略

JIT的优化策略有很多,这里主要介绍两种比较普遍的优化,帮助大家理解JIT理解JIT优化的细节。

  1. 使用寄存器优化
    编译器通过决定何时从主存取值,何时向寄存器充值,来优化程序执行,减少开销。
    Java Code 的例子
public class RegisterTest {
        private int sum;

        public void calculateSum(int n) {
        for (int i = 0; i < n; ++i) {
            sum += i;
        }
    }
}

在上面的例子中,sum的值在某些时刻可能在主存中,但是从主存中检索值是开销很大的操作。循环中可能多次从主存取值,性能很低。JIT编译器寄存器优化策略,会加载一个寄存器给sum并赋予其初始值,sum值在寄存器里循环,循环结束后,将最终的结果从寄存器返回给主存。这样就省去了从主存中检索值得开销。

  1. 使用方法内联优化
    方法内联就是把方法的代码“复制”到发起调用的方法里,以消除方法调用。
    Java Code 的例子
public void caller(){
        int a=1;
        int b=2;
        //do sth
        int result =sum(a,b);
    }

    public int sum(int x,int y){
        return x+y;
    }

经过JIT编译器优化后,可能是转换为

public void caller(){
        int a=1;
        int b=2;
        //do sth
        int result =a+b;
    }

PS:以上例子只是帮助理解,并不能准确的反映JIT的方法内联优化。

JIT编译器优化实践

下面是一个简单的JIT优化的例子

package test.jit;
/**
 * -XX:CompileThreshold=100000
 * @author marshall
 *
 */
public class JITTest {

    public static int sum(int x, int y) {
        int a = x + 1;
        int b = y + 1;
        int rs = a + b;
        return rs;
    }

    public static int total(int n) {
        int res = 0;
        for (int i = 0; i < n; i++) {
            res += sum(i, i);
        }
        return res;
    }

    public static void main(String[] args) {
        int rs;
        long bf;
        long af;
        long beforeJIT;
        long afterJIT;
        bf = System.nanoTime();
        rs = total(100000);
        af = System.nanoTime();
        beforeJIT = af - bf;
        System.out.println("程序没有经过预热即没有经过JIT优化,花费时间:" + beforeJIT + " 纳秒");

        bf = System.nanoTime();
        rs = total(100000);
        af = System.nanoTime();
        afterJIT = af - bf;
        System.out.println("程序经过预热即经过JIT优化,花费时间:" + afterJIT + " 纳秒");

        System.out.println("减少开销:" + (beforeJIT - afterJIT) / (beforeJIT * 1.0));

    }

}

手动设置程序阈值为100000,经过多次测试,运行测试越多, JIT优化性能提升越大。

IJ Java编译器怎么设置快捷输入 java jit编译器_IJ Java编译器怎么设置快捷输入