阿里巴巴Java性能调优实战:深入JVM即时编译器JIT,优化Java编译
深入JVM即时编译器JIT,优化Java编译
然而许多 Java 开发人员对 JIT 编译器的了解并不多,不深挖其工作原理,也不深究如何检 测应用程序的即时编译情况,线上发生问题后很难做到从容应对。
类编译加载执行过程
类编译
在编写好代码之后,我们需要将 .java 文件编译成 .class 文件,才能在虚拟机上正常运行代 码。文件的编译通常是由 JDK 中自带的 Javac 工具完成,一个简单的 java 文件,我们可 以通过 javac 命令来生成 .class 文件。
下面我们通过 javap反编译来看看一个 class 文件结构中主要包含了哪些信息:
看似一个简单的命令执行,前期编译的过程其实是非常复杂的,包括词法分析、填充符号 表、注解处理、语义分析以及生成 class 文件,这个过程我们不用过多关注。只要从上图中 知道,编译后的字节码文件主要包括常量池和方法表集合这两部分就可以了。
类加载
当一个类被创建实例或者被其它对象引用时,虚拟机在没有加载过该类的情况下,会通过类 加载器将字节码文件加载到内存中。
类连接
类在加载进来之后,会进行连接、初始化,最后才会被使用。在连接过程中,又包括验证、 准备和解析三个部分。
类初始化
即时编译
即时编译器类型
在 HotSpot 虚拟机中,内置了两个 JIT,分别为 C1 编译器和 C2 编译器,这两个编译器的 编译过程是不一样的。
第 0 层:程序解释执行,默认开启性能监控功能(Profiling),如果不开启,可触发第 二层编译;
第 1 层:可称为 C1 编译,将字节码编译为本地代码,进行简单、可靠的优化,不开启 Profiling;
第 2 层:也称为 C1 编译,开启 Profiling,仅执行带方法调用次数和循环回边执行次数 profiling 的 C1 编译;
第 3 层:也称为 C1 编译,执行所有带 Profiling 的 C1 编译;
第 4 层:可称为 C2 编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。
热点探测
在 HotSpot 虚拟机中的热点探测是 JIT 优化的条件,热点探测是基于计数器的热点探测, 采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数,如果执行次数超过一 定的阈值就认为它是“热点方法” 。
虚拟机为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计 数器(Back Edge Counter)。在确定虚拟机运行参数的前提下,这两个计数器都有一个 确定的阈值,当计数器超过阈值溢出了,就会触发 JIT 编译。
编译优化技术
JIT 编译运用了一些经典的编译优化技术来实现代码的优化,即通过一些例行检查优化,可 以智能地编译出运行时的最优性能代码。今天我们主要来学习以下两种优化手段:
1. 方法内联
那么对于那些方法体代码不是很大,又频繁调用的方法来说,这个时间和空间的消耗会很 大。方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实 的方法调用。
2. 逃逸分析
逃逸分析(Escape Analysis)是判断一个对象是否被外部方法引用或外部线程访问的分析 技术,编译器会根据逃逸分析的结果对代码进行优化。
栈上分配
然后,我们分别设置 VM 参数:Xmx1000m -Xms1000m -XX:-DoEscapeAnalysis - XX:+PrintGC 以及 -Xmx1000m -Xms1000m -XX:+DoEscapeAnalysis -XX:+PrintGC, 通过之前讲过的 VisualVM 工具,查看堆中创建的对象数量。
然而,运行结果却没有达到我们想要的优化效果,也许你怀疑是 JDK 版本的问题,然而我 分别在 1.6~1.8 版本都测试过了,效果还是一样的:
(-server -Xmx1000m -Xms1000m -XX:-DoEscapeAnalysis -XX:+PrintGC)
(-server -Xmx1000m -Xms1000m -XX:+DoEscapeAnalysis -XX:+PrintGC)
这其实是因为由于 HotSpot 虚拟机目前的实现导致栈上分配实现比较复杂,可以说,在 HotSpot 中暂时没有实现这项优化。随着即时编译器的发展与逃逸分析技术的逐渐成熟, 相信不久的将来 HotSpot 也会实现这项优化功能。