什么是JIT

Just In Time(即时编译)
一般程序遵循90-10原则,即运行时的90%时间里计算机是在处理其中10%的代码,java也是,jvm频繁的解释字节码也挺累的,因为还要翻译成机器码 ,所以JIT的宗旨就是在运行过程当中找出热点代码并编译成二进制的机器语言(深度优化),这样就能很好的提高java的执行效率

假如Java的某个方法有1M次调用,通过jit优化此方法之后前后差100个指令,那么1M次调用,你就节省了0.1G个指令(1GHZ=10^3MHZ=10^6KHZ=10^9HZ)
假设cpu处理频率为2.5GHz,优化后变为2.6GHz,相当于提升了4%的性能。如果优化的更多或者调用次数更多则性能提升的空间越大

一般都会优化哪些内容?

  1. 死代码删除
    如下代码,变量i是无任何意义的

    1
    2
    3
    4
    public static void main(String[] args) {
    int i = 1;
    System.out.println("hello world");
    }
  2. 方法内联
    jit中非常重要的一环 将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段
    大白话就是a调用b,为了减少入栈、出栈,直接把b的代码编译在a中

  3. 逃逸分析
    在栈中new的对象是否出栈之后就再无引用,那么就可以在栈上分配,出栈即刻销毁
    锁消除--编译器仅需证明锁对象不逃逸出线程,便可以进行锁消除,比上面栈上分配的技术难度更大一些(基于逃逸分析的锁消除实际上并不多见)

  4. 循环展开
    循环展开通过减少或消除控制程序循环的指令,争取在一次循环就把所有任务做完

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     for (int i = 0; i < 5; i++) {
    System.out.println(i);
    }
    // 优化后
    for (;;) {
    System.out.println(1);
    System.out.println(2);
    System.out.println(3);
    System.out.println(4);
    System.out.println(5);
    break;
    }
  5. 窥孔优化与寄存器分配
    y1=x1*3 经过优化后得到 y1=(x1<<1)+x1
    寄存器分配指的是把频繁使用的变量保存在寄存器中,CPU访问寄存器的速度比内存快得多,可以提升程序的运行速度

优化引擎

hotspot jvm 内置了两个jit编译器,分别是client compiler(C1编译器)和server compiler(C2编译器)

  • c1=-client
    方法内联、死代码删除
  • c2=-server
    几乎会执行所有经典的优化工作,如:无用代码消除、循环展开、循环表达式外提、消除公共子表达式、常量传播、基本块重排序、Java语言紧密相关的优化技术(范围检查消除、空值检查消除)、分支频率预测等

Graal VM比c2编译优化的还更高级,具体可以参考其他资料


safePoint

GC的时候会暂停JVM所有的线程,此时jvm状态为STW(stop the world)。那么和安全点有什么关系呢?
安全点的主要作用就是代表当前的线程可以安全的暂停,恢复之后还可以正常执行
如果没有在安全点暂停的话,GC回收垃圾时,对象的实际地址会有变动,如果不在安全点暂停,那么持有的对象的地址可能因此而错乱,所以必须要在安全点暂停
安全点是由各个线程主动中断的(自己说了算),主动中断是设置一个中断标志,各个线程运行到SafePoint的时候主动轮询这个标志,一旦发现中断标志为 True,就会在自己最近的“安全点”上主动中断挂起


安全点都有哪些呢?

安全点位置的选取基本上是以“超长时间执行的特征”为标准进行选定的,“长时间执行”的最明显特征就是指令序列的复用(包含一系列的指令)
不同JVM安全点的位置也不同,安全点放置过多也会性能,JIT编译的代码里会在所有方法的

  1. 临返回之前

  2. 所有非counted loop的循环的回跳之前放置安全点

    counted loop意思是for循环数字且有明确的数量(边界),使用启动参数-XX:+UseCountedLoopSafepoints,可以在CountedLoop回跳之前放置安全点

  3. 方法调用

  4. 循环跳转

  5. 异常跳转

所有能够修改JVM执行状态的JNI函数在入口处都有安全点检查,如果中断标志为 True,则会进入安全点并阻塞等待
其余正在执行的JNI函数可以在安全点上运行,因为它们只使用句柄,但在返回之前会主动检查安全点状态,如果中断标志为 True,则会进入安全点并阻塞等待

在SafePoint上不代表被阻塞(比如:JNI方法就可以在SafePoint上运行),但是被阻塞一定发生在SafePoint上


什么时候会用到安全点?

  1. GC时需要全局安全点,需要注意,如果有一个线程没有进入到安全点时(安全区的线程除外),GC无法展开工作,导致停顿时间延长

    能够确保在某一段代码片段之中,引用关系不会发生变化称之为安全区
    在执行到安全区域里面的代码时,首先会标识自己在安全区(比如Sleep或Blocked的线程),GC时会忽略此线程,离开安全区之前必须要接收到JVM可以离开的信号,比如GC未完成则不允许离开安全区

  2. jit逆优化(重新修改方法体redefineClass)
  3. 偏向锁升级为轻量级锁时