解释器不是一个巨大的 switch,HotSpot 是在启动时为每个字节码生成一小段机器码。这篇把这些机器码打印出来,反汇编一个,再量解释执行有多慢、什么时候被编译。
JDK 21: 代码总量 81K,codelet 总数 275,平均 301;有字节码编号的 codelet 239 个(0–238),其中 fast_* / nofast_* 的「改写后字节码」33 个
JDK 21 部分字节码 codelet 的机器码大小(字节): {'nop': 56, 'iconst_1': 64, 'iload_0': 64, 'iload': 152, 'iadd': 32, 'idiv': 48, 'iinc': 128, 'ifeq': 616, 'if_icmplt': 624, 'goto': 600, 'getfield': 520, 'fast_igetfield': 64, 'putfield': 1152, 'new': 384, 'invokestatic': 640, 'invokevirtual': 1160, 'invokeinterface': 2592, 'ireturn': 784, 'aastore': 1008, 'athrow': 16}
最小的 5 个: [('lookupswitch', 16), ('athrow', 16), ('istore_0', 24), ('istore_1', 24), ('istore_2', 24)] ;最大的 5 个: [('return_register_finalizer', 840), ('aastore', 1008), ('putfield', 1152), ('invokevirtual', 1160), ('invokeinterface', 2592)]
大小分布: {'0-32': 60, '33-64': 75, '65-128': 44, '129-700': 47, '701-5000': 13}
JDK 27: 代码总量 85K,codelet 总数 277,平均 316;有字节码编号的 codelet 239 个(0–238),其中 fast_* / nofast_* 的「改写后字节码」33 个
JDK 27 同样几个 codelet 的大小: {'iconst_1': 64, 'iload_0': 64, 'iadd': 32, 'iinc': 128, 'if_icmplt': 624, 'goto': 600, 'invokevirtual': 1264, 'ireturn': 952} iadd(32 字节 = 8 个指令字):
ldr w0, [x20], #8
ldr w1, [x20], #8
add w0, w1, w0
ldrb w8, [x22, #1]!
add w9, w8, #1024
ldr x9, [x21, w9, uxtw #3]
br x9
nop
iload_0(64 字节 = 16 个指令字):
str x0, [x20, #-8]!
b #36
str s0, [x20, #-8]!
b #28
str d0, [x20, #-16]!
b #20
str xzr, [x20, #-8]!
str x0, [x20, #-8]!
b #8
str x0, [x20, #-8]!
ldr x0, [x24]
ldrb w8, [x22, #1]!
add w9, w8, #1024
ldr x9, [x21, w9, uxtw #3]
br x9
noploop(int) 的字节码: 0:lconst_0 | 1:lstore_1 | 2:iconst_0 | 3:istore_3 | 4:iload_3 | 5:iload_0 | 6:if_icmpge 20 | 9:lload_1 | 10:iload_3 | 11:i2l | 12:ladd | 13:lstore_1 | 14:iinc 3, 1 | 17:goto 4 | 20:lload_1 | 21:lreturn 每次迭代执行的字节码数(从循环头 4 到 goto 17,按 javap 计数): 10 -Xint(只用解释器) 每次迭代 4.383 ns;折合每个字节码 0.44 ns 只用 C1(-XX:TieredStopAtLevel=1) 每次迭代 0.299 ns;折合每个字节码 0.03 ns 默认分层编译(C1 + C2) 每次迭代 0.307 ns;折合每个字节码 0.03 ns
JVM 选项: 默认(Tier3InvocationThreshold=200,Tier3MinInvocationThreshold=100,Tier3CompileThreshold=2000)
调用 50 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 150 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 199 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 200 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 201 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 210 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 250 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 300 次 → 28 6 3 Main::hot (6 bytes)
调用 500 次 → 19 6 3 Main::hot (6 bytes)
调用 1000 次 → 17 6 3 Main::hot (6 bytes)
调用 1001 次 → 27 6 3 Main::hot (6 bytes)
调用 1100 次 → 24 6 3 Main::hot (6 bytes)
调用 1500 次 → 28 6 3 Main::hot (6 bytes)
调用 5000 次 → 20 6 3 Main::hot (6 bytes) ; 21 7 4 Main::hot (6 bytes) ; 21 6 3 Main::hot (6 bytes) made not entrant
调用 20000 次 → 20 6 3 Main::hot (6 bytes) ; 20 7 4 Main::hot (6 bytes) ; 21 6 3 Main::hot (6 bytes) made not entrant
JVM 选项: ['-XX:Tier3InvocationThreshold=1000', '-XX:Tier3MinInvocationThreshold=1000']
调用 50 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 150 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 199 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 200 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 201 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 210 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 250 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 300 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 500 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 1000 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 1001 次 → 没有出现在 PrintCompilation 里(没有被编译)
调用 1100 次 → 21 2 3 Main::hot (6 bytes)
调用 1500 次 → 26 2 3 Main::hot (6 bytes)
调用 5000 次 → 17 2 3 Main::hot (6 bytes)
调用 20000 次 → 24 2 3 Main::hot (6 bytes) ; 24 3 4 Main::hot (6 bytes) ; 24 2 3 Main::hot (6 bytes) made not entrant 循环 10000 次 → 没有 Main::main 的编译记录
循环 40000 次 → 没有 Main::main 的编译记录
循环 60000 次 → 没有 Main::main 的编译记录
循环 60500 次 → 27 6 % 3 Main::main @ 12 (60 bytes)
循环 62000 次 → 27 6 % 3 Main::main @ 12 (60 bytes)
循环 65000 次 → 27 6 % 3 Main::main @ 12 (60 bytes)
循环 70000 次 → 26 6 % 3 Main::main @ 12 (60 bytes)
循环 200000 次 → 22 6 % 3 Main::main @ 12 (60 bytes) ; 22 7 3 Main::main (60 bytes) ; 22 8 % 4 Main::main @ 12 (60 bytes) ; 23 6 % 3 Main::main @ 12 (60 bytes) made not entrant
循环 2000000 次 → 22 6 % 3 Main::main @ 12 (60 bytes) ; 22 7 3 Main::main (60 bytes) ; 23 8 % 4 Main::main @ 12 (60 bytes) ; 24 6 % 3 Main::main @ 12 (60 bytes) made not entrant ; 25 8 % 4 Main::main @ 12 (60 bytes) made not entrant {'Tier0InvokeNotifyFreqLog': 7, 'Tier0BackedgeNotifyFreqLog': 10, 'Tier3InvocationThreshold': 200, 'Tier3MinInvocationThreshold': 100, 'Tier3BackEdgeThreshold': 60000}
调用计数每 128 次通知一次:大于 Tier3InvocationThreshold=200 的第一个计数点是 256;大于 1000 的第一个计数点是 1024
回边计数每 1024 次通知一次:大于 Tier3BackEdgeThreshold=60000 的第一个计数点是 60416