Ответ 1
Короткие: компилятор Just In Time не работает.
Прежде всего, вы можете использовать опцию -XX:+PrintCompilation
, чтобы увидеть КОГДА JIT что-то делает. Затем вы увидите что-то вроде этого:
$ java -XX:+PrintCompilation weird
168 1 weird$CountByOne::getNext (28 bytes)
174 1 % weird::main @ 18 (220 bytes)
279 1 % weird::main @ -2 (220 bytes) made not entrant
113727636
280 2 % weird::main @ 91 (220 bytes)
106265475
427228826
Итак, вы видите, что метод main скомпилирован иногда во время первого и второго блоков.
Добавление параметров -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOption
даст вам больше информации о том, что делает JIT. Обратите внимание, для этого требуется hsdis-amd64.so
, который, как представляется, не очень хорошо доступен в распространенных дистрибутивах Linux. Возможно, вам придется скомпилировать его самостоятельно из OpenJDK.
Что вы получаете, это огромный кусок кода ассемблера для getNext и main.
Для меня в первой компиляции кажется, что только первый блок в основном компилируется, вы можете указать номера строк. Он содержит такие забавные вещи:
0x00007fa35505fc5b: add $0x1,%r8 ;*ladd
; - weird$CountByOne::[email protected] (line 12)
; - weird::[email protected] (line 31)
0x00007fa35505fc5f: mov %r8,0x10(%rbx) ;*putfield i
; - weird$CountByOne::[email protected] (line 12)
; - weird::[email protected] (line 31)
0x00007fa35505fc63: add $0x1,%r14 ;*ladd
; - weird::[email protected] (line 31)
(Действительно, он очень длинный из-за разворачивания и вставки петли)
В процессе перекомпиляции main, второй и третий блок компилируется. Второй блок выглядит очень похоже на первую версию. (Снова просто выдержка)
0x00007fa35505f05d: add $0x1,%r8 ;*ladd
; - weird$CountByOne::[email protected] (line 12)
; - weird::[email protected] (line 42)
0x00007fa35505f061: mov %r8,0x10(%rbx) ;*putfield i
; - weird$CountByOne::[email protected] (line 12)
; - weird::[email protected] (line 42)
0x00007fa35505f065: add $0x1,%r13 ;*ladd
ОДНАКО третий блок скомпилирован по-разному. Без инкрустации и разворачивания
На этот раз весь цикл выглядит следующим образом:
0x00007fa35505f20c: xor %r10d,%r10d
0x00007fa35505f20f: xor %r8d,%r8d ;*lload
; - weird::[email protected] (line 53)
0x00007fa35505f212: mov %r8d,0x10(%rsp)
0x00007fa35505f217: mov %r10,0x8(%rsp)
0x00007fa35505f21c: mov %rbp,%rsi
0x00007fa35505f21f: callq 0x00007fa355037c60 ; OopMap{rbp=Oop off=580}
;*invokevirtual getNext
; - weird::[email protected] (line 53)
; {optimized virtual_call}
0x00007fa35505f224: mov 0x8(%rsp),%r10
0x00007fa35505f229: add %rax,%r10 ;*ladd
; - weird::[email protected] (line 53)
0x00007fa35505f22c: mov 0x10(%rsp),%r8d
0x00007fa35505f231: inc %r8d ;*iinc
; - weird::[email protected] (line 52)
0x00007fa35505f234: cmp $0x5f5e100,%r8d
0x00007fa35505f23b: jl 0x00007fa35505f212 ;*if_icmpge
; - weird::[email protected] (line 52)
Я предполагаю, что JIT определил, что эта часть кода не используется много, поскольку она использовала профилирующую информацию из второго исполнения блока и, следовательно, не очень оптимизировала ее. Кроме того, JIT представляется ленивым в некотором смысле не перекомпилировать один метод после того, как все соответствующие части были скомпилированы. Помните, что первый результат компиляции не содержал исходный код для второго/третьего блока AT, поэтому JIT пришлось перекомпилировать это.