「JVM 编译优化」即时编译器

文章介绍了JVM中的解释器和编译器的作用,特别是即时编译器(如HotSpot的C1和C2)如何将Java字节码转换为本地机器码以提高性能。讨论了热点代码的检测方法、分层编译策略以及编译过程中的优化技术,包括编译触发条件、编译对象和编译过程的详细步骤。此外,还提到了如何通过JVM参数观察和分析即时编译的结果。
摘要由CSDN通过智能技术生成
  • 前端编译器(javac)将 Java 代码转为字节码(抽象语法树),优化手段主要用于提升程序的编码效率;
  • 后端编译器(内置于 JVM 的 JIT/AOT Compiler,C1,C2)将字节码转为本地机器码,其编译速度及编译结果质量是衡量 JVM 性能的最重要指标;

主流的商用 JVM(HotSpot、OpenJ9)最初都是通过解释器(Interpreter)进行解释执行的(JRockit 没有解释器),在运行时,VM 为了提高热点代码的执行效率,会将之编译成本地代码,并加诸各种代码优化手段;在运行时完成这些任务的后端编译器被称为即时编译器;

  • 热点代码Hot Spot Code),当 VM 发现某个方法或代码块运行特别频繁,就会把这些代码认定为热点代码;

1. 解释器与编译器

解释器 vs. 编译器

  • 解释器:
    • 当程序需要迅速启动和执行时,解释器可以省去编译时间,立即运行;
    • 当程序内存资源限制较大,解释执行可以节约内存;
    • 解释器还可以作为编译器激进优化不成立时的逃生门,让编译器可以进行一些不能保证一定正确的激进优化手段(如加载新类,类型继承结构出现变化、出现罕见陷阱(Uncommon Trap),可以哦太难过逆优化(Deoptimization)退回解释执行状态);
  • 编译器:
    • 当程序启动后,越来越多热点代码被编译成本地代码,就省去了解释器的中间损耗,执行效率更高;

请添加图片描述

HotSpot 中内置了三个即时编译器,其中两个存在已久(C1:Client Compiler;C2:Server Compiler),第三个 Graal 编译器是在 JDK 10 才出现的,长期目标是代替 C2;

分层编译Tiered Compilation)模式之前,HotSpot VM 通常采用解释器加一个编译器(C1 或 C2)的混合模式Mixed Mode),编译器的选择取决于 HotSpot VM 的运行模式(HotSpot VM 自动根据宿主机硬件性能自动选择,或用户使用 -client-server 参数强制指定;

用户还可以使用 -Xint 强制 JVM 使用解释模式Interpreted Mode),让编译器完全不介入工作;
也可以使用 -Xcomp 强制 JVM 使用编译模式Compiled Mode),优先使用编译方式执行,解释器只在编译无法进行时介入执行;

java -version
openjdk version "1.8.0_362"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_362-b09)
OpenJDK 64-Bit Server VM (Temurin)(build 25.362-b09, mixed mode)

java -Xint -version
openjdk version "1.8.0_362"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_362-b09)
OpenJDK 64-Bit Server VM (Temurin)(build 25.362-b09, interpreted mode)

java -Xcomp -version
openjdk version "1.8.0_362"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_362-b09)
OpenJDK 64-Bit Server VM (Temurin)(build 25.362-b09, compiled mode)

即时编译会占用程序运行时间,编译优化程度越高的代码,所需时间越长;解释器会为编译器收集性能监控信息,用于提升编译器优化效果,为了在程序响应速度与运行效率之间达成最佳平衡,HotSpot VM 引入了分层编译(JDK 7 的 Server Mode 成为默认编译策略);

  • 第 0 层,程序纯解释执行,解释器不开启性能监控功能(Profiling);
  • 第 1 层,使用 C1 将字节码编译为本地代码来运行,进行简单可靠的稳定优化,不开启性能监控功能;
  • 第 2 层,使用 C1 执行,仅开启方法及回边次数统计等有限的性能监控功能;
  • 第 3 层,使用 C1 执行,开启全部性能监控,除了第 2 层的统计信息外,还会收集如分支跳转、虚方法调用版本等全部的统计信息;
  • 第 4 层,使用 C2 将字节码编译为本地代码,相比起 C1,C2 会启用更多编译耗时更长的优化,还会根据性能监控信息进行一些不可靠的激进优化;

请添加图片描述

实施分层编译后,解释器、C1、C2 编译器会同时工作,热点代码可能会被多次编译;在解释执行时无需额外承担收集性能监控信息的任务,C2 采用高复杂度优化算法进行编译时,C1 可以采用简单优化为其争取更多时间;

2. 编译对象与触发条件

即时编译的目标:热点代码

  • 多次被调用的方法,直接编译目标方法;
  • 多次被执行的循环体,编译循环体所在的整个方法;执行入口有所不同,编译时会传入执行入口字节码序号(Byte Code Index,BCI);这种编译发生在方法执行过程中,方法的栈帧还在栈上,方法就被替换了,因此也叫栈上替换(On Stack Replacement,OSR);

热点探测(Hot Spot Code Detection)

  • 基于采样Sample Based Hot Spot Code Detection),JVM 周期性的检查各个线程的调用栈顶,经常出现在栈顶的方法就是热点方法;简单高效,容易获得方法调用关系(展开堆栈),但难以精确获得方法热度,容易收到线程阻塞或其他外界因素影响;(J9)
  • 基于计数器Counter Based Hot Spot Code Detection),JVM 为每个方法(代码块)建立计数器,统计方法的执行次数,执行次数超过一定阈值的就是热点方法;维护计数器麻烦,且无法直接获得方法调用关系,但统计结果精确严谨;(HotSpot)

还存在一种基于跟踪Trace)的热点探测(FireFox 的 TraceMonkey 和 Dalvik 的即时编译器);

HotSpot 的两类计数器

  • 方法调用计数器(Invocation Counter),统计方法被调用的次数;
  • 回边计数器(Back Edge Coutner),统计一个方法中循环体代码执行的次数,回边指在控制流向回跳转的指令(循环边界往回跳转);设计目的是为了触发栈上的替换编译(也可以应付常见的跑分测试);

当一个方法被调用,VM 会先检查该方法是否已存在即时编译过的版本,若存在,优先使用编译后的本地代码,若不存在,则将其方法调用计数器加 1,让后判断方法调用计数器回边计数器之和是否超过方法调用计数器;超过则向即时编译器提交一个该方法的代码编译请求;

请添加图片描述

默认执行引擎不会同步等待编译请求完成,而是先继续使用解释器执行字节码,直到提交的编译请求被完成,这个方法的调用入口地址就会被系统自动改写为新值;下次调用该方法时就会使用已编译的版本;

  • 热度衰减Counter Decay),方法调用计数器记录的并不是方法被调用的绝对次数,而是一段时间内方法被调用的次数(为了体现出方法执行的频率);当一段时间方法调用次数没有达到编译阈值时,方法调用计数器会减少一半,即热度的衰减,减半的时间间隔即为半衰周期Counter Half Life Time);衰减动作在 GC 时顺带进行;

  • -XX:-UseCounterDecay,可以关闭热度衰减,这样只要系统运行足够长的时间,程序绝大部分方法将被编译成本地代码;

  • -XX:CounterHalfLifeTime,可以设置半衰周期长度,单位为秒;

  • -XX:CompileThreshold,方法调用计数器阈值;Client Mode 的默认阈值是 1500 次,Server Mode 是 10000 次;

  • -XX:BackEdgeThreshold,回边计数器阈值;

  • -XX:OnStackReplacePercentage,间接调整回边计数器的阈值;

  • -XX:InterpreterProfilePercentage,解释器监控比率;

回边计数器阈值的两种计算公式

BackEdgeThreshold = CompileThreshold * OnStackReplacePercentage / 100

其中 OnStackReplacePercentage 的默认值为 933;计算得 BackEdgeThreshold 为 13995;

BackEdgeThreshold = CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercentage) / 100

其中 OnStackReplacePercentage 的默认值为 140;InterpreterProfilePercentage 的默认值为 33;计算得 BackEdgeThreshold 为 10700;

当解释器遇到一条回边指令,会先检查将要执行的代码片段是否已存在编译好的版本,若有,则优先执行已编译的代码,否则将回边计数器加 1,再判断方法调用计数器回边计数器之和是否超过回边计数器的阈值;若超过,则提交一个栈上替换编译请求,并把回边计数器的值稍微降低,以便继续使用解释器执行循环;

请添加图片描述

回边计数器没有热度衰减,当回边计数器溢出时,方法计数器的值也会被调整到溢出状态,以保证下次进入该方法时会执行标准编译过程;

MethodOop.hpp in HotSpot VM

// |------------------------------------------------------|
// | header                                               |
// | klass                                                |
// |------------------------------------------------------|
// | constMethodOop                 (oop)                 |
// | constants                      (oop)                 |
// |------------------------------------------------------|
// | methodData                     (oop)                 |
// | interp_invocation_count                              |
// |------------------------------------------------------|
// | access_flags                                         |
// | vtable_index                                         |
// |------------------------------------------------------|
// | result_index (C++ interpreter only)                  |
// |------------------------------------------------------|
// | method_size            | max_stack                   |
// | max_locals             | size_of_parameters          |
// |------------------------------------------------------|
// |intrinsic_id  | flags   | throwout_count              |
// |------------------------------------------------------|
// | num_breakpoints        | (unused)                    |
// |------------------------------------------------------|
// | invocation_counter                                   |
// | backedge_counter                                     |
// |------------------------------------------------------|
// |        prev_time (tiered only, 64 bit wide)          |
// |                                                      |
// |------------------------------------------------------|
// |                    rate (tiered)                     |
// |------------------------------------------------------|
// | code                           (pointer)             |
// | i2i                            (pointer)             |
// | adapter                        (pointer)             |
// | from_compiled_entry            (pointer)             |
// | from_interpreted_entry         (pointer)             |
// |------------------------------------------------------|
// | native_function        (present only if native)      |
// | signature_handler      (present only if native)      |
// |------------------------------------------------------|

上文为 MethodOop.hpp 的注释,描述的是方法内存布局,其中每一行表示占用 32 bit,从中可以看到方法调用计数器和回边计数器所在的位置与数据宽度,还有 from_compiled_entry 与 from_interpreted_entry 两个方法的入口位置;

3. 编译过程

  • -XX:-BackgroundCompilation,禁用后台编译,用户现场会在提交编译请求后阻塞,直到编译完成,直接执行编译输出的本地代码;

C1 编译器的编译过程

关注局部性能,放弃了耗时较长的全局优化手段;

请添加图片描述

  • 第一阶段,在平台独立的前端字节码上完成一部分基础优化(如方法内联、常量传播等),让后将之构造成高级中间代码表示High-Level Intermediate Representation,HIR,即与目标机器指令集无关的中间表示);HIR 使用静态单分配(Static Single Assignment,SSA)形式代表代码值;
  • 第二阶段,在 HIR 上完成一些优化(如空值检查消除、范围检查消除等),然后通过 HIR 生成平台相关的低级中间代码表示(Low-Level Intermediate Representation,LIR,即与目标机器指令集相关的中间表示);
  • 第三阶段,使用线性扫描算法Linear Scan Register Allocation)在 LIR 上分配寄存器,并在 LIR 上做窥孔(Peephole)优化,然后产生机器代码;

C2 编译器的编译

为服务端的性能配置针对性调整,可容忍高复杂度的优化(几乎达到 GNU C++ 编译器使用 -O2 参数时的优化强度);

  • 经典优化
    • 无用代码消除Dead Code Elimination
    • 循环展开Loop Unrolling
    • 循环表达式外提Loop Expression Hoisting
    • 消除公共子表达式Common Subexpression Elimination
    • 常量传播Constant Propagation
    • 基本块重排序Basic Block Reordering
  • Java 语言特性相关的优化
    • 范围检查消除Range Check Elimination
    • 空值检查消除Null Check Elimination
  • 激进优化(根据解释器或 C1 提供的性能监控信息,进行一些不稳定的预测性激进优化)
    • 守护内联Guarded Inlining
    • 分支频率预测Branch Frequency Prediction

C2 的寄存器分配器是一个全局着色分配器,可以充分利用如 RISC 处理器架构的大寄存器集合

C2 的编译虽慢,但也远远高于传统的静态优化编译器,且相对 C1 编译输出的代码质量有很大提升,可以大幅减少执行时间;

4. 查看及分析即时编译结果

HotSpot VM 提供了一些参数用来输出即时编译和某些优化措施的运行状况,以满足调试和调优的需要(部分参数需要 FastDebug 或 SlowDebug 优化级别下才能支持);

测试代码示例

public static final int NUM = 15000;

public static int doubleValue(int i) {
    for(int j=0; j<100000; j++);
    return i * 2;
}

public static long calcSum() {
    long sum = 0;
    for (int i = 1; i <= 100; i++) {
        sum += doubleValue(i);
    }
    return sum;
}

public static void main(String[] args) {
    for (int i = 0; i < NUM; i++) {
        calcSum();
    }
}

-XX:+PrintCompilation,打印将要被编译成本地代码的方法名称;

    509    1     n 0       java.lang.System::arraycopy (native)   (static)
    511    2       3       java.lang.StringBuilder::append (8 bytes)
    514    3       4       java.lang.String::charAt (29 bytes)
    516    4 %     4       java.lang.String::indexOf @ 37 (70 bytes)
    526    5       1       java.lang.ref.Reference::get (5 bytes)
    526    7       4       java.lang.String::hashCode (55 bytes)
    529    6       3       java.lang.Math::min (11 bytes)
    530    9       3       java.lang.String::length (6 bytes)
    530    8       3       java.lang.String::<init> (82 bytes)
    530   11     n 0       java.lang.Thread::currentThread (native)   (static)
    532   10       3       java.lang.String::startsWith (72 bytes)
    533   13       3       java.lang.Object::<init> (1 bytes)
    533   12       3       java.util.Arrays::copyOf (19 bytes)
    536   14       3       java.io.UnixFileSystem::normalize (75 bytes)
    537   19       4       java.lang.String::indexOf (70 bytes)
    538   18       3       sun.nio.cs.UTF_8$Encoder::encode (359 bytes)
    539   17       3       java.lang.String::equals (81 bytes)
    541   21       3       java.lang.CharacterData::of (120 bytes)
    542   22       3       java.lang.CharacterDataLatin1::getProperties (11 bytes)
    542   16       3       sun.nio.fs.UnixPath::checkNotNul (16 bytes)
    542   20       3       java.lang.String::indexOf (7 bytes)
    542   23       3       java.util.HashMap::hash (20 bytes)
    542   15       3       java.io.UnixFileSystem::isInvalid (17 bytes)
    543   24 %     3       edu.aurelius.jvm.jit.Test::doubleValue @ 2 (18 bytes)
    543   25       1       java.lang.Object::<init> (1 bytes)
    544   13       3       java.lang.Object::<init> (1 bytes)   made not entrant
    545   26       3       edu.aurelius.jvm.jit.Test::doubleValue (18 bytes)
    545   27 %     4       edu.aurelius.jvm.jit.Test::doubleValue @ 2 (18 bytes)
    547   24 %     3       edu.aurelius.jvm.jit.Test::doubleValue @ -2 (18 bytes)   made not entrant
    548   28       4       edu.aurelius.jvm.jit.Test::doubleValue (18 bytes)
    550   26       3       edu.aurelius.jvm.jit.Test::doubleValue (18 bytes)   made not entrant
    550   29       3       edu.aurelius.jvm.jit.Test::calcSum (26 bytes)
    552   30 %     4       edu.aurelius.jvm.jit.Test::calcSum @ 4 (26 bytes)
    554   31       4       edu.aurelius.jvm.jit.Test::calcSum (26 bytes)
    556   29       3       edu.aurelius.jvm.jit.Test::calcSum (26 bytes)   made not entrant

-XX:+PrintInlining,打印方法内联信息;

    148    1       1       sun.instrument.TransformerManager::getSnapshotTransformerList (5 bytes)
    152    2       3       java.lang.Object::<init> (1 bytes)
    154    3     n 0       java.lang.System::arraycopy (native)   (static)
    158    4 %     4       java.lang.String::indexOf @ 37 (70 bytes)
    158    5       3       java.lang.StringBuilder::append (8 bytes)
                              @ 2   java.lang.AbstractStringBuilder::append (50 bytes)   callee is too large
    160   12       4       java.lang.String::charAt (29 bytes)
    160    8       3       java.lang.String::indexOf (166 bytes)
    163   11       3       java.lang.String::startsWith (72 bytes)
    163   15       4       java.lang.String::hashCode (55 bytes)
    163    9       3       java.lang.Math::min (11 bytes)
    164   10       3       java.lang.String::length (6 bytes)
    164   13       1       java.lang.ref.Reference::get (5 bytes)
    164    6       3       java.util.zip.ZipFile::ensureOpen (37 bytes)
                              @ 13  java/lang/IllegalStateException::<init> (not loaded)   not inlineable
                              @ 32  java/lang/IllegalStateException::<init> (not loaded)   not inlineable
    165   16       3       java.lang.String::<init> (82 bytes)
                              @ 1   java.lang.Object::<init> (1 bytes)
                              @ 13  java/lang/StringIndexOutOfBoundsException::<init> (not loaded)   not inlineable
                              @ 30  java/lang/StringIndexOutOfBoundsException::<init> (not loaded)   not inlineable
                              @ 65  java/lang/StringIndexOutOfBoundsException::<init> (not loaded)   not inlineable
                              @ 75   java.util.Arrays::copyOfRange (63 bytes)   callee is too large
    167   17       3       java.io.UnixFileSystem::normalize (75 bytes)
                              @ 1   java.lang.String::length (6 bytes)
                              @ 19   java.lang.String::charAt (29 bytes)
                                @ 18  java/lang/StringIndexOutOfBoundsException::<init> (not loaded)   not inlineable
                              @ 44   java.io.UnixFileSystem::normalize (132 bytes)   callee is too large
                              @ 69   java.io.UnixFileSystem::normalize (132 bytes)   callee is too large
    169    7       3       java.util.zip.ZipCoder::getBytes (192 bytes)
                              @ 1   java.util.zip.ZipCoder::encoder (35 bytes)
                                @ 12   java.nio.charset.Charset::newEncoder (0 bytes)   no static binding
                                @ 18   java.nio.charset.CharsetEncoder::onMalformedInput (26 bytes)
                                  @ 21   java.nio.charset.CharsetEncoder::implOnMalformedInput (1 bytes)
                                @ 24   java.nio.charset.CharsetEncoder::onUnmappableCharacter (26 bytes)
                                  @ 21   java.nio.charset.CharsetEncoder::implOnUnmappableCharacter (1 bytes)
                              @ 4   java.nio.charset.CharsetEncoder::reset (11 bytes)
                                @ 1   java.nio.charset.CharsetEncoder::implReset (1 bytes)
                              @ 9   java.lang.String::toCharArray (25 bytes)
                                @ 20   java.lang.System::arraycopy (0 bytes)   intrinsic
                              @ 17   java.nio.charset.CharsetEncoder::maxBytesPerChar (5 bytes)
                              @ 62   sun.nio.cs.UTF_8$Encoder::encode (359 bytes)   callee is too large
                              @ 81   java.lang.IllegalArgumentException::<init> (6 bytes)   don't inline Throwable constructors
                              @ 89   java.util.Arrays::copyOf (19 bytes)
                                @ 11   java.lang.Math::min (11 bytes)
                                @ 14   java.lang.System::arraycopy (0 bytes)   intrinsic
                              @ 95   java.nio.ByteBuffer::wrap (8 bytes)
               !                @ 4   java.nio.ByteBuffer::wrap (20 bytes)
                                  @ 7   java.nio.HeapByteBuffer::<init> (14 bytes)
                                    @ 10   java.nio.ByteBuffer::<init> (45 bytes)   callee is too large
                                  @ 16  java/lang/IndexOutOfBoundsException::<init> (not loaded)   not inlineable
                              @ 101   java.nio.CharBuffer::wrap (8 bytes)
               !                @ 4   java.nio.CharBuffer::wrap (20 bytes)
                                  @ 7   java.nio.HeapCharBuffer::<init> (14 bytes)
                                    @ 10   java.nio.CharBuffer::<init> (22 bytes)
                                      @ 6   java.nio.Buffer::<init> (121 bytes)   callee is too large
                                  @ 16  java/lang/IndexOutOfBoundsException::<init> (not loaded)   not inlineable
               !              @ 112   java.nio.charset.CharsetEncoder::encode (285 bytes)   callee is too large
                              @ 119   java.nio.charset.CoderResult::isUnderflow (13 bytes)
                              @ 131   java.nio.charset.CoderResult::toString (52 bytes)   callee is too large
                              @ 134   java.lang.IllegalArgumentException::<init> (6 bytes)   don't inline Throwable constructors
                              @ 141   java.nio.charset.CharsetEncoder::flush (49 bytes)   callee is too large
                              @ 148   java.nio.charset.CoderResult::isUnderflow (13 bytes)
                              @ 160   java.nio.charset.CoderResult::toString (52 bytes)   callee is too large
                              @ 163   java.lang.IllegalArgumentException::<init> (6 bytes)   don't inline Throwable constructors
                              @ 169   java.nio.Buffer::position (5 bytes)
                              @ 185   java.nio.Buffer::position (5 bytes)
                              @ 188   java.util.Arrays::copyOf (19 bytes)
                                @ 11   java.lang.Math::min (11 bytes)
                                @ 14   java.lang.System::arraycopy (0 bytes)   intrinsic
    173   20 %     3       edu.aurelius.jvm.jit.Test::doubleValue @ 2 (18 bytes)
    174   21       3       sun.nio.cs.UTF_8$Encoder::encode (359 bytes)
                              @ 14   java.lang.Math::min (11 bytes)
                              @ 139   java.lang.Character::isSurrogate (18 bytes)
                              @ 157  sun/nio/cs/Surrogate$Parser::<init> (not loaded)   not inlineable
                              @ 175  sun/nio/cs/Surrogate$Parser::parse (not loaded)   not inlineable
                              @ 186   java.nio.charset.CharsetEncoder::malformedInputAction (5 bytes)
    174   22 %     4       edu.aurelius.jvm.jit.Test::doubleValue @ 2 (18 bytes)
    175   19       3       java.lang.String::equals (81 bytes)
    176   23       4       java.lang.String::indexOf (70 bytes)
    176   18       3       java.util.HashMap::hash (20 bytes)
    176   20 %     3       edu.aurelius.jvm.jit.Test::doubleValue @ -2 (18 bytes)   made not entrant
                              @ 9   java.lang.Object::hashCode (0 bytes)   no static binding
    178   14       3       java.util.Arrays::copyOf (19 bytes)
                              @ 11   java.lang.Math::min (11 bytes)
                              @ 14   java.lang.System::arraycopy (0 bytes)   intrinsic
    178   24       4       edu.aurelius.jvm.jit.Test::doubleValue (18 bytes)
    179   25       3       java.lang.String::indexOf (7 bytes)
                              @ 3   java.lang.String::indexOf (70 bytes)   inlining prohibited by policy
    180   26       3       java.lang.CharacterData::of (120 bytes)
    181   27       3       java.lang.CharacterDataLatin1::getProperties (11 bytes)
    181   28       3       edu.aurelius.jvm.jit.Test::calcSum (26 bytes)
                              @ 12   edu.aurelius.jvm.jit.Test::doubleValue (18 bytes)   inlining prohibited by policy
    182   29 %     4       edu.aurelius.jvm.jit.Test::calcSum @ 4 (26 bytes)
                              @ 12   edu.aurelius.jvm.jit.Test::doubleValue (18 bytes)   inline (hot)
    187   30       4       edu.aurelius.jvm.jit.Test::calcSum (26 bytes)
    188   31       1       java.lang.Object::<init> (1 bytes)
                              @ 12   edu.aurelius.jvm.jit.Test::doubleValue (18 bytes)   inline (hot)
    189    2       3       java.lang.Object::<init> (1 bytes)   made not entrant
    189   28       3       edu.aurelius.jvm.jit.Test::calcSum (26 bytes)   made not entrant

doubleValue() 被内联到了 calcSum(),calcSum() 又被内联编译到了 main() 中;JVM 在执行 main() 时,calcSum() 和 doubleValue() 不会被实际调用,没有任何方法分派的开销;

  • -XX:+PrintAssembly,JVM 提供了一组反汇编接口,可以接入各平台下反汇编适配器(如 x86_32 的 hsdis-i386、x86_64 的 hsdis-amd64、hsdis-sparc、hsdis-sparcv9、hsdis-aarch64 等),放入 JAVA_HOME/lib/amd64/server(与 jvm.dll 或 libjvm.so 在相同路径即可),通过 -XX:+PrintAssembly 开启打印编译方法的汇编代码;需要 FastDebug 或 SlowDebug 优化级别的 HotSpot VM,或者开启 -XX:+UnlockDiagnosticVMOptions

  • -XX:+PrintOptoAssembly,相比 PrintAssembly 可以输出更多信息(注释);

  • -XX:+PrintCFGToFile,输出 C1 编译过程各阶段(字节码、HIR 生成、LIR 生成、寄存器分配过程、本地代码生成等)的数据到文件;

  • -XX:+PrintIdealGraphFile,输出 C2 编译过程各阶段的数据到文件;

  • Java HotSpot Client Compiler Visualizer,用于分析客户端编译器;

  • Ideal Graph Visualizer,用于分析服务端编译器;

  • Ideal Graph理想图),服务端编译器的中间表示,一种程序依赖图(Program Dependence Graph,PDG);

通过 -XX:PrintIdealGraphLevel=2 -XX:PrintIdealGraphFile=ideal.xml 在即时编译后生成一个名为 ideal.xml 的文件,它包含服务端编译器编译代码的全过程信息,可以通过 Ideal Graph Visualizer 进行查看和分析;

  • Basic Block(程序基本块),程序按照控制流分割出来的最小代码块;它只能有一个入口和一个出口,只要基本块中的第一条指令被执行,基本块的所有指令都会按照顺序全部执行一次;
  • After Parsing,服务端编译器刚完成解析(字节码 -> 中间表示),还没有做任何优化的理想图表示;
  • Final Code,消除空循环,一些语言安全保障措施和 GC 安全点轮询操作也被消除了(编译器判定没有这些保障措施程序运行结果相同);空循环在本地代码中不会被执行;

doubleValue() 的简单执行顺序

a. 程序入口,建立栈帧;
b. 设置 j=0,进行安全点(Safepoint)轮询,跳转到 4 的条件检查;
c. 执行 j++;
d. 条件检查,如果 j<100000,跳转到 3;
e. 设置 i=i*2,进行安全点轮询,函数返回;

实际形成的理想图结果比这个过程负责得多,需要考虑安全(类型安全、空指针检查)和 JVM 的运作需求(Safepoint 轮询);

doubleValue() 方法的编译结果存在标准编译和栈上替换编译两个版本;

空循环对方法的运算结果不会产生影响,但如果没有任何优化,执行循环就会耗费处理器时间;


上一篇:JVM 编译优化」插入式注解处理器(自定义代码编译检查)
下一篇:「JVM 编译优化」提前编译器

PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!


参考资料:

  • [1]《深入理解 Java 虚拟机》
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三余知行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值