As JohannesD suggested in a comment, it's hardly possible to count from 0 to Integer.MAX_VALUE
(and, after the overflow, from -Integer.MAX_VALUE
to 0 again) so quickly.
In order to verify the assumption that the JIT does some magic optimization here, I created a slightly modified program, introducing some methods make it easier to identify parts of the code:
class IntOverflowTest
{
public static void main(String[] args) {
runLoop();
}
public static void runLoop()
{
int i = 1;
int k = 0;
while (true) {
if(++i==0) doPrint(++k);
}
}
public static void doPrint(int k)
{
System.out.println("loop: " + k);
}
}
The bytecode emitted and shown with javap -c IntOverflowTest
brings no surprises:
class IntOverflowTest {
IntOverflowTest();
Code:
0: aload_0
1: invokespecial #1
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2
3: return
public static void runLoop();
Code:
0: iconst_1
1: istore_0
2: iconst_0
3: istore_1
4: iinc 0, 1
7: iload_0
8: ifne 4
11: iinc 1, 1
14: iload_1
15: invokestatic #3
18: goto 4
public static void doPrint(int);
Code:
0: getstatic #4
3: new #5
6: dup
7: invokespecial #6
10: ldc #7
12: invokevirtual #8
15: iload_0
16: invokevirtual #9
19: invokevirtual #10
22: invokevirtual #11
25: return
}
It clearly does increment both local variables (runLoop
, offsets 4 and 11).
However, when running the code with -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:+PrintAssembly
in a Hotspot Disassembler, the machine code eventually ends up to be the following:
Decoding compiled method 0x00000000025c2c50:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x000000001bb40408} 'runLoop' '()V' in 'IntOverflowTest'
# [sp+0x20] (sp of caller)
0x00000000025c2da0: mov %eax,-0x6000(%rsp)
0x00000000025c2da7: push %rbp
0x00000000025c2da8: sub $0x10,%rsp ;*synchronization entry
; - IntOverflowTest::runLoop@-1 (line 10)
0x00000000025c2dac: mov $0x1,%ebp ;*iinc
; - IntOverflowTest::runLoop@11 (line 13)
0x00000000025c2db1: mov %ebp,%edx
0x00000000025c2db3: callq 0x00000000024f6360 ; OopMap{off=24}
;*invokestatic doPrint
; - IntOverflowTest::runLoop@15 (line 13)
; {static_call}
0x00000000025c2db8: inc %ebp ;*iinc
; - IntOverflowTest::runLoop@11 (line 13)
0x00000000025c2dba: jmp 0x00000000025c2db1 ;*invokestatic doPrint
; - IntOverflowTest::runLoop@15 (line 13)
0x00000000025c2dbc: mov %rax,%rdx
0x00000000025c2dbf: add $0x10,%rsp
0x00000000025c2dc3: pop %rbp
0x00000000025c2dc4: jmpq 0x00000000025b0d20 ; {runtime_call}
0x00000000025c2dc9: hlt
One can clearly see that it does not increment the outer variable i
any more. It only calls the doPrint
method, increments a single variable (k
in the code), and then and immediately jumps back to the point before the doPrint
call.
So the JIT indeed seems to detect that there is no real "condition" involved for printing the output, and that the code is equivalent to an infinite loop that only prints and increments a single variable.
This seems like a quite sophisticated optimization for me. I would expect that it's far from trivial to detect a case like this. But obviously, they managed to do so...