前端编译器
(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 虚拟机》