前端编译器
(javac)将 Java 代码转为字节码(抽象语法树),优化手段主要用于提升程序的编码效率;后端编译器
(内置于 JVM 的 JIT/AOT Compiler,C1,C2)将字节码转为本地机器码,其编译速度及编译结果质量是衡量 JVM 性能的最重要指标;主流的商用 JVM(HotSpot、OpenJ9)最初都是通过解释器(Interpreter)进行解释执行的(JRockit 没有解释器),在运行时,VM 为了提高热点代码的执行效率,会将之编译成本地代码,并加诸各种代码优化手段;在运行时完成这些任务的后端编译器被称为即时编译器;
热点代码
(Hot Spot Code
),当 VM 发现某个方法或代码块运行特别频繁,就会把这些代码认定为热点代码;解释器 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 可以采用简单优化为其争取更多时间;
即时编译的目标:热点代码
整个方法
;执行入口有所不同,编译时会传入执行入口字节码序号(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 两个方法的入口位置;
-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
)范围检查消除
(Range Check Elimination
)空值检查消除
(Null Check Elimination
)守护内联
(Guarded Inlining
)分支频率预测
(Branch Frequency Prediction
)C2 的寄存器分配器是一个全局着色分配器
,可以充分利用如 RISC
处理器架构的大寄存器集合
;
C2 的编译虽慢,但也远远高于传统的静态优化编译器
,且相对 C1 编译输出的代码质量有很大提升,可以大幅减少执行时间;
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:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!
参考资料: