Java的即时编译器

日常的所谓的Java编译, 一般指的是前端编译, 也就是javac编译, 将写的.java文件, 编译成.class文件, 这个过程里javac并不做性能优化, 只会做语法检查. 而相应的后端编译, 指的就是将字节码转换成在操作系统执行的机器码的过程. 这个过程中, Java采用的混合的办法, 包括解释执行和编译执行, 其中的编译执行, 就是所谓的即使编译器的作用. 这篇文章下面所指的编译, 主要就是指这个过程的编译.

分层编译模式

Hotspot虚拟机有多个即时编译器, C1(Client Compiler)和C2(Server Compiler). 当然最新的还有Graal即时编译器.

Java7之前, 对于启动性有较高要求的, 我们选择C1, 对应的参数为 -client. 对于性能要求比较高的, 使用 -server 来启动C2编译器.

Java7开始, 引入了分层编译的概念,对应参数-XX:+TieredCompilation, 它综合了C1的启动优势和C2巅峰性能优势. 分层编译将jvm的执行状态分为五个层次:

  • 解释执行
  • 执行不带profiling(例如JDK自带的hprof)的C1代码(C1生成的机器码)
  • 执行部分profiling的C1代码
  • 执行所有profiling的C1代码
  • 执行C2代码

一般C2代码的执行效率要高于C1 30%以上. 上面C1的三个层次, 当然是越来越慢的. 热点的代码会被C1编译, 然后再被C4编译.

Java8是默认开启了分层编译, 原来的参数-client 和 -server参数都无效, 如果关闭分层编译, jvm将直接采用C2. 如果你希望只用C1, 那么在开启分层编译时可以用参数-XX:TieredStopAtLevel=1, 这时,在解释执行后,直接由C1进行编译.

即时编译的触发

jvm是根据方法的调用次数和循环回边的执行次数来触发即时编译的. 那么什么是循环回边? 例如下面的代码:

public static void foo(Object obj) {
  int sum = 0;
  for (int i = 0; i < 10; i++) {
    sum += i;
  }
}

代码在执行时, 循环会一次次执行, 每结束一次循环, 回到下一次循环的起点继续执行, 而循环的两个边界, 就是循环回边. 在解释执行时, 每运行一次, jvm就会将该方法的循环回边计数器 +1. 如果是C1, 它也是在这加循环回边计数器的代码. 这个计数器是不做同步的, 所以数值不一定准确, 但是可以确定的是足够大就可以说明他是热点方法了.

当不启用分层编译的情况下, 方法的调用次数 + 循环回边的次数和超过-XX:CompileThreshold指定的值时, 就会触发即时编译(C1默认1500,C2默认10000).

当启动分层编译时, 参数-XX:CompileThreshold将会失效, 阀值的大小是动态的.

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 原文:https://insistence.cnblogs.com/p/5901457.html 1. 什么是Ju...
    laosijikaichele阅读 831评论 0 2
  • javac做了些什么? 说白了,javac就是一个编译器;编译器就是把一种语言规矩转换成另一种语言规矩,也就是将对...
    西部小笼包阅读 4,885评论 0 4
  • 部分的商用虚拟机中,Java程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会...
    胡二囧阅读 861评论 0 1
  • 三年前因为想打发晚上的无聊时间,报了一个会计培训的课程。每天晚上去上课的日子,开始变得很匆忙。经过三个月的学习,终...
    薛薛闲扯阅读 337评论 0 4
  • 婺源有个石城村,每到秋季,满山的枫叶一片红色。 婺源的秋天,比春天要更好看,在我看来既是如此,油菜花毕竟随处可见,...
    吴晓布阅读 1,870评论 22 27