对下面这种带有简单无限循环的Java程序,
HotSpot的JIT编译器会:
1、client模式:执行命令:
C1会编译代码,会将循环内无用的代码都消除掉,但不会把循环本身消除:
上面位于0x00bc6820和0x00bc6826的两条指令就是无限循环的残余物:
原本在循环内的代码(int i = 1;)已经消失了,剩下的是在回边处对safepoint的轮询(test),以及循环末尾的无条件跳转(jmp)。
从编译记录看,int i = 1;对应的代码是在寄存器分配过程中被削除的。
2、server模式:执行命令:
C2会拒绝编译这种代码,并打出日志:
然后foo()中的无限循环就一直在解释器中执行下去了。
在hotspot/src/share/vm/opto/compile.cpp中,bool Compile::final_graph_reshaping()的代码前面有这样的注释:
======================================================================
上面的实验中,如果把foo中的无限循环在源码上就变为空循环的话:
则无论是在client还是server模式都不会触发对该方法的JIT编译,一直在解释器中去执行那个循环。
查看javac编译生成的字节码,可以确认该循环在字节码中是存在的:
foo()方法中唯一的一条字节码指令就是goto 0了。有趣的是因为foo()带有无限循环,所以编译出来的字节码里连return都没有。
HotSpot解释器触发JIT编译,是通过两个计数器来进行的:方法调用计数器与回边计数器,当这两个计数器的和超过了方法调用或循环次数的预设阈值就触发对某个方法的JIT编译;这两个计数器在每个方法里都有自己的一份。其中,方法调用计数器自然是用来记录某个方法被调用的次数的,而回变计数器则用于记录某方法中所有循环执行的次数(以方法而不是单个循环为粒度)。一个空的无限循环在字节码中的表现是一条goto字节码指令,参数是字节码的相对偏移量,并且该偏移量为0(意味着它指向该goto指令自身)。解释器中有这样的代码:
此时EDX持有相对偏移量,下面的JNS指令会在EDX为非负值的时候执行跳转——正好跳过了计数器自增的代码;这样就只有真正“向后跳”的goto才会使回边计数器自增。也就是说空的无限循环不会引起回边计数器的累加,默认配置下也就不会触发HotSpot进行JIT编译。
======================================================================
* 本文的测试环境是32位Windows XP SP3,E8400,Sun JDK 1.6.0 update 18
// java -XX:+PrintCompilation -XX:+PrintAssembly TestC2InfiniteLoop
public class TestC2InfiniteLoop {
public static void foo() {
while (true) { int i = 1; }
}
public static void main(String[] args) {
foo();
}
}HotSpot的JIT编译器会:
1、client模式:执行命令:
java -XX:+PrintCompilation -XX:+PrintAssembly TestC2InfiniteLoopC1会编译代码,会将循环内无用的代码都消除掉,但不会把循环本身消除:
1% TestC2InfiniteLoop::foo @ 0 (5 bytes)
Decoding compiled method 0x00bc6748:
Code:
[Disassembling for mach='i386']
[Entry Point]
[Verified Entry Point]
;; block B2 [0, 0]
0x00bc6810: mov %eax,-0x4000(%esp)
0x00bc6817: push %ebp
0x00bc6818: mov %esp,%ebp
0x00bc681a: sub $0x18,%esp ;*iconst_1
; - TestC2InfiniteLoop::foo@0 (line 5)
;; block B3 [0, 0]
0x00bc681d: nop
0x00bc681e: nop
0x00bc681f: nop ; OopMap{off=16}
;*goto
; - TestC2InfiniteLoop::foo@2 (line 5)
;; block B0 [0, 2]
0x00bc6820: test %eax,0x970100 ; {poll}
;; 26 branch [AL] [B0]
0x00bc6826: jmp 0x00bc6820 ;*goto
; - TestC2InfiniteLoop::foo@2 (line 5)
;; block B1 [0, 0]
0x00bc6828: mov %eax,-0x4000(%esp)
0x00bc682f: push %ebp
0x00bc6830: mov %esp,%ebp
0x00bc6832: sub $0x18,%esp
0x00bc6835: mov %ecx,(%esp)
0x00bc6838: call 0x082ea120 ; {runtime_call}
;; 20 branch [AL] [B0]
0x00bc683d: jmp 0x00bc6820
0x00bc683f: nop
0x00bc6840: nop
0x00bc6841: hlt
0x00bc6842: hlt
0x00bc6843: hlt
0x00bc6844: hlt
0x00bc6845: hlt
0x00bc6846: hlt
0x00bc6847: hlt
0x00bc6848: hlt
0x00bc6849: hlt
0x00bc684a: hlt
0x00bc684b: hlt
0x00bc684c: hlt
0x00bc684d: hlt
0x00bc684e: hlt
0x00bc684f: hlt
[Exception Handler]
[Stub Code]
0x00bc6850: mov $0xdead,%ebx ; {no_reloc}
0x00bc6855: mov $0xdead,%ecx
0x00bc685a: mov $0xdead,%edx
0x00bc685f: mov $0xdead,%esi
0x00bc6864: mov $0xdead,%edi
0x00bc6869: jmp 0x00bc1d60 ; {runtime_call}
0x00bc686e: push $0xbc686e ; {section_word}
0x00bc6873: jmp 0x00b7ba40 ; {runtime_call}上面位于0x00bc6820和0x00bc6826的两条指令就是无限循环的残余物:
0x00bc6820: test %eax,0x970100 ; {poll}
;; 26 branch [AL] [B0]
0x00bc6826: jmp 0x00bc6820 ;*goto
; - TestC2InfiniteLoop::foo@2 (line 5)原本在循环内的代码(int i = 1;)已经消失了,剩下的是在回边处对safepoint的轮询(test),以及循环末尾的无条件跳转(jmp)。
从编译记录看,int i = 1;对应的代码是在寄存器分配过程中被削除的。
2、server模式:执行命令:
java -server -XX:+PrintCompilation -XX:+PrintAssembly TestC2InfiniteLoopC2会拒绝编译这种代码,并打出日志:
1% TestC2InfiniteLoop::foo @ 0 (5 bytes)
1 COMPILE SKIPPED: trivial infinite loop (not retryable)然后foo()中的无限循环就一直在解释器中执行下去了。
在hotspot/src/share/vm/opto/compile.cpp中,bool Compile::final_graph_reshaping()的代码前面有这样的注释:
// (4) Detect infinite loops; blobs of code reachable from above but not
// below. Several of the Code_Gen algorithms fail on such code shapes,
// so we simply bail out. Happens a lot in ZKM.jar, but also happens
// from time to time in other codes (such as -Xcomp finalizer loops, etc).
// Detection is by looking for IfNodes where only 1 projection is
// reachable from below or CatchNodes missing some targets.
// ...
bool Compile::final_graph_reshaping() {
// an infinite loop may have been eliminated by the optimizer,
// in which case the graph will be empty.
if (root()->req() == 1) {
record_method_not_compilable("trivial infinite loop");
return true;
}
// ...
}======================================================================
上面的实验中,如果把foo中的无限循环在源码上就变为空循环的话:
public static void foo() {
while (true) ;
}则无论是在client还是server模式都不会触发对该方法的JIT编译,一直在解释器中去执行那个循环。
查看javac编译生成的字节码,可以确认该循环在字节码中是存在的:
public static void foo();
Signature: ()V
Code:
Stack=0, Locals=0, Args_size=0
0: goto 0
LineNumberTable:
line 5: 0
StackMapTable: number_of_entries = 1
frame_type = 0 /* same */foo()方法中唯一的一条字节码指令就是goto 0了。有趣的是因为foo()带有无限循环,所以编译出来的字节码里连return都没有。
HotSpot解释器触发JIT编译,是通过两个计数器来进行的:方法调用计数器与回边计数器,当这两个计数器的和超过了方法调用或循环次数的预设阈值就触发对某个方法的JIT编译;这两个计数器在每个方法里都有自己的一份。其中,方法调用计数器自然是用来记录某个方法被调用的次数的,而回变计数器则用于记录某方法中所有循环执行的次数(以方法而不是单个循环为粒度)。一个空的无限循环在字节码中的表现是一条goto字节码指令,参数是字节码的相对偏移量,并且该偏移量为0(意味着它指向该goto指令自身)。解释器中有这样的代码:
0x0097e781: test %edx,%edx
0x0097e783: jns 0x0097e7a7此时EDX持有相对偏移量,下面的JNS指令会在EDX为非负值的时候执行跳转——正好跳过了计数器自增的代码;这样就只有真正“向后跳”的goto才会使回边计数器自增。也就是说空的无限循环不会引起回边计数器的累加,默认配置下也就不会触发HotSpot进行JIT编译。
======================================================================
* 本文的测试环境是32位Windows XP SP3,E8400,Sun JDK 1.6.0 update 18
2703

被折叠的 条评论
为什么被折叠?



