热点代码:编程
虚拟机中的字节码(.class文件内容)是由解释器( Interpreter )完成编译的,当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会把这些代码认定为“热点代码”。服务器
什么是jit编译:app
为了提升热点代码的执行效率,在运行时,即时编译器(JIT)会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,而后保存到内存中。编程语言
在 HotSpot 虚拟机中,内置了两个 JIT,分别为 C1 编译器和 C2 编译器,这两个编译器的编译过程是不同的。模块化
C1 编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,例如,GUI 应用对界面启动速度就有必定要求。性能
C2 编译器是为长期运行的服务器端应用程序作性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序。根据各自的适配性,这两种即时编译也被称为 Client Compiler 和 Server Compiler。优化
如何探测代码为热点代码:ui
热点探测是基于计数器的热点探测,采用这种方法的虚拟机会为每一个方法创建计数器统计方法的执行次数,若是执行次数超过必定的阈值就认为它是“热点方法”。线程
虚拟机为每一个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter;在程序中遇到控制流向后跳转的指令称为“回边”)。code
jit编译优化技术:
1. 方法内联
调用一个方法一般要经历压栈和出栈,这种执行操做要求在执行前保护现场并记忆执行的地址,并按原来保存的地址返回执行。 所以,方法调用会产生必定的时间和空间方面的开销。
方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。
ps:但要强调一点,热点方法不必定会被 JVM 作内联优化,若是这个方法体太大了,JVM 将不执行内联操做。而方法体的大小阈值,咱们也能够经过参数设置
2. 逃逸分析
逃逸分析(Escape Analysis)是判断一个对象是否被外部方法引用或外部线程访问的分析技术,编译器会根据逃逸分析的结果对代码进行优化。
2.1.栈上分配
咱们知道,在 Java 中默认建立一个对象是在堆中分配内存的,而当堆内存中的对象再也不使用时,则须要经过垃圾回收机制回收,这个过程相对分配在栈中的对象的建立和销毁来讲,更消耗时间和性能。这个时候,逃逸分析若是发现一个对象只在方法中使用,就会将对象分配在栈上。
2.2.锁消除
StringBuffer 中的 append 方法被 Synchronized 关键字修饰,会使用到锁,从而致使性能降低。
但实际上,一个局部方法内使用StringBuffer 和 StringBuilder 的性能基本没什么区别。这是由于在局部方法中建立的对象只能被当前线程访问,没法被其它线程访问,这个变量的读写确定不会有竞争,这个时候 JIT 编译会对这个对象的方法锁进行锁消除。
2.3.标量替换
逃逸分析证实一个对象不会被外部访问,若是这个对象能够被拆分的话,当程序真正执行的时候可能不建立这个对象,而直接建立它的成员变量来代替。将对象拆分后,能够分配对象的成员变量在栈或寄存器上,本来的对象就无需分配内存空间了。这种编译优化就叫作标量替换。
如:
public void foo() {
TestInfo info = new TestInfo();
info.id = 1;
info.count = 99;
...//to do something
}
逃逸分析后,代码会被优化为:
public void foo() {
id = 1;
count = 99;
...//to do something
}
在 Java8 以前,HotSpot 集成了两个 JIT,用 C1 和 C2 来完成 JVM 中的即时编译。虽然 JIT 优化了代码,但收集监控信息会消耗运行时的性能,且编译过程会占用程序的运行时间。
到了 Java9,AOT 编译器被引入。和 JIT 不一样,AOT 是在程序运行前进行的静态编译,这样就能够避免运行时的编译消耗和内存消耗,且 .class 文件经过 AOT 编译器是能够编译成 .so 的二进制文件的。
到了 Java10,一个新的 JIT 编译器 Graal 被引入。Graal 是一个以 Java 为主要编程语言、面向 Java bytecode 的编译器。与用 C++ 实现的 C1 和 C2 相比,它的模块化更加明显,也更容易维护。Graal 既能够做为动态编译器,在运行时编译热点方法;也能够做为静态编译器,实现 AOT 编译。