Java 编译器在第一次访问后省略 getfield 操作码是否合法?
Would it be legal for a Java compiler to omit getfield opcodes after the first access?
我正在试验 some C# code 的 Java 端口,我很惊讶地看到 javac 1.8.0_60 每次发出一个 getfield
操作码访问了对象字段。
这里是 Java 代码:
public class BigInteger
{
private int[] bits;
private int sign;
//...
public byte[] ToByteArray()
{
if (sign == 0)
{
return new byte[] { 0 };
}
byte highByte;
int nonZeroDwordIndex = 0;
int highDword;
if (bits == null)
{
highByte = (byte)((sign < 0) ? 0xff : 0x00);
highDword = sign;
}
else if (sign == -1)
{
highByte = (byte)0xff;
assert bits.length > 0;
assert bits[bits.length - 1] != 0;
while (bits[nonZeroDwordIndex] == 0)
{
nonZeroDwordIndex++;
}
highDword = ~bits[bits.length - 1];
if (bits.length - 1 == nonZeroDwordIndex)
{
highDword += 1;
}
}
else
{
assert sign == 1;
highByte = 0x00;
highDword = bits[bits.length - 1];
}
byte msb;
int msbIndex;
if ((msb = (byte)(highDword >>> 24)) != highByte)
{
msbIndex = 3;
}
else if ((msb = (byte)(highDword >>> 16)) != highByte)
{
msbIndex = 2;
}
else if ((msb = (byte)(highDword >>> 8)) != highByte)
{
msbIndex = 1;
}
else
{
msb = (byte)highDword;
msbIndex = 0;
}
boolean needExtraByte = (msb & 0x80) != (highByte & 0x80);
byte[] bytes;
int curByte = 0;
if (bits == null)
{
bytes = new byte[msbIndex + 1 + (needExtraByte ? 1 : 0)];
assert bytes.length <= 4;
}
else
{
bytes = new byte[4 * (bits.length - 1) + msbIndex + 1 + (needExtraByte ? 1 : 0)];
for (int i = 0; i < bits.length - 1; i++)
{
int dword = bits[i];
if (sign == -1)
{
dword = ~dword;
if (i <= nonZeroDwordIndex)
{
dword = dword + 1;
}
}
for (int j = 0; j < 4; j++)
{
bytes[curByte++] = (byte)dword;
dword >>>= 8;
}
}
}
for (int j = 0; j <= msbIndex; j++)
{
bytes[curByte++] = (byte)highDword;
highDword >>>= 8;
}
if (needExtraByte)
{
bytes[bytes.length - 1] = highByte;
}
return bytes;
}
}
据 javap 报告,javac 1.8.0_60 生成以下字节码:
public byte[] ToByteArray();
Code:
0: aload_0
1: getfield #3 // Field sign:I
4: ifne 15
7: iconst_1
8: newarray byte
10: dup
11: iconst_0
12: iconst_0
13: bastore
14: areturn
15: iconst_0
16: istore_2
17: aload_0
18: getfield #2 // Field bits:[I
21: ifnonnull 48
24: aload_0
25: getfield #3 // Field sign:I
28: ifge 37
31: sipush 255
34: goto 38
37: iconst_0
38: i2b
39: istore_1
40: aload_0
41: getfield #3 // Field sign:I
44: istore_3
45: goto 193
48: aload_0
49: getfield #3 // Field sign:I
52: iconst_m1
53: if_icmpne 156
56: iconst_m1
57: istore_1
58: getstatic #11 // Field $assertionsDisabled:Z
61: ifne 80
64: aload_0
65: getfield #2 // Field bits:[I
68: arraylength
69: ifgt 80
72: new #12 // class java/lang/AssertionError
75: dup
76: invokespecial #13 // Method java/lang/AssertionError."":()V
79: athrow
80: getstatic #11 // Field $assertionsDisabled:Z
83: ifne 109
86: aload_0
87: getfield #2 // Field bits:[I
90: aload_0
91: getfield #2 // Field bits:[I
94: arraylength
95: iconst_1
96: isub
97: iaload
98: ifne 109
101: new #12 // class java/lang/AssertionError
104: dup
105: invokespecial #13 // Method java/lang/AssertionError."":()V
108: athrow
109: aload_0
110: getfield #2 // Field bits:[I
113: iload_2
114: iaload
115: ifne 124
118: iinc 2, 1
121: goto 109
124: aload_0
125: getfield #2 // Field bits:[I
128: aload_0
129: getfield #2 // Field bits:[I
132: arraylength
133: iconst_1
134: isub
135: iaload
136: iconst_m1
137: ixor
138: istore_3
139: aload_0
140: getfield #2 // Field bits:[I
143: arraylength
144: iconst_1
145: isub
146: iload_2
147: if_icmpne 193
150: iinc 3, 1
153: goto 193
156: getstatic #11 // Field $assertionsDisabled:Z
159: ifne 178
162: aload_0
163: getfield #3 // Field sign:I
166: iconst_1
167: if_icmpeq 178
170: new #12 // class java/lang/AssertionError
173: dup
174: invokespecial #13 // Method java/lang/AssertionError."":()V
177: athrow
178: iconst_0
179: istore_1
180: aload_0
181: getfield #2 // Field bits:[I
184: aload_0
185: getfield #2 // Field bits:[I
188: arraylength
189: iconst_1
190: isub
191: iaload
192: istore_3
193: iload_3
194: bipush 24
196: iushr
197: i2b
198: dup
199: istore 4
201: iload_1
202: if_icmpeq 211
205: iconst_3
206: istore 5
208: goto 254
211: iload_3
212: bipush 16
214: iushr
215: i2b
216: dup
217: istore 4
219: iload_1
220: if_icmpeq 229
223: iconst_2
224: istore 5
226: goto 254
229: iload_3
230: bipush 8
232: iushr
233: i2b
234: dup
235: istore 4
237: iload_1
238: if_icmpeq 247
241: iconst_1
242: istore 5
244: goto 254
247: iload_3
248: i2b
249: istore 4
251: iconst_0
252: istore 5
254: iload 4
256: sipush 128
259: iand
260: iload_1
261: sipush 128
264: iand
265: if_icmpeq 272
268: iconst_1
269: goto 273
272: iconst_0
273: istore 6
275: iconst_0
276: istore 8
278: aload_0
279: getfield #2 // Field bits:[I
282: ifnonnull 325
285: iload 5
287: iconst_1
288: iadd
289: iload 6
291: ifeq 298
294: iconst_1
295: goto 299
298: iconst_0
299: iadd
300: newarray byte
302: astore 7
304: getstatic #11 // Field $assertionsDisabled:Z
307: ifne 443
310: aload 7
312: arraylength
313: iconst_4
314: if_icmple 443
317: new #12 // class java/lang/AssertionError
320: dup
321: invokespecial #13 // Method java/lang/AssertionError."":()V
324: athrow
325: iconst_4
326: aload_0
327: getfield #2 // Field bits:[I
330: arraylength
331: iconst_1
332: isub
333: imul
334: iload 5
336: iadd
337: iconst_1
338: iadd
339: iload 6
341: ifeq 348
344: iconst_1
345: goto 349
348: iconst_0
349: iadd
350: newarray byte
352: astore 7
354: iconst_0
355: istore 9
357: iload 9
359: aload_0
360: getfield #2 // Field bits:[I
363: arraylength
364: iconst_1
365: isub
366: if_icmpge 443
369: aload_0
370: getfield #2 // Field bits:[I
373: iload 9
375: iaload
376: istore 10
378: aload_0
379: getfield #3 // Field sign:I
382: iconst_m1
383: if_icmpne 404
386: iload 10
388: iconst_m1
389: ixor
390: istore 10
392: iload 9
394: iload_2
395: if_icmpgt 404
398: iload 10
400: iconst_1
401: iadd
402: istore 10
404: iconst_0
405: istore 11
407: iload 11
409: iconst_4
410: if_icmpge 437
413: aload 7
415: iload 8
417: iinc 8, 1
420: iload 10
422: i2b
423: bastore
424: iload 10
426: bipush 8
428: iushr
429: istore 10
431: iinc 11, 1
434: goto 407
437: iinc 9, 1
440: goto 357
443: iconst_0
444: istore 9
446: iload 9
448: iload 5
450: if_icmpgt 474
453: aload 7
455: iload 8
457: iinc 8, 1
460: iload_3
461: i2b
462: bastore
463: iload_3
464: bipush 8
466: iushr
467: istore_3
468: iinc 9, 1
471: goto 446
474: iload 6
476: ifeq 488
479: aload 7
481: aload 7
483: arraylength
484: iconst_1
485: isub
486: iload_1
487: bastore
488: aload 7
490: areturn
请注意,每次访问 sign
和 bits
字段时,编译器都会发出 getfield
操作码。
阅读 JLS8 的第 17.4.5 节,发生在订单之前,我不明白为什么每次 sign
和 [=15= 时都需要发出 getfield
操作码] 字段被访问(第一次除外)。
Java 编译器仅发出两个 getfield
操作码并将当时可见的字段值保存在框架局部变量中是否合法?
这不仅是合法的,而且一旦代码被 JIT 编译器编译(表达式提升是可用的优化之一)就可能发生。
例如下面的代码:
public class Test {
private boolean stop;
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
new Thread(t::m).start();
// Thread.sleep(1000);
System.out.println("stop is now true");
t.stop = true;
}
private void m() {
while (!stop);
System.out.println("Finished");
}
}
立即终止(至少在我的机器上)。这不能保证,但因为每次都获取该字段,所以有一个点可以传播和捕获更改。
但是,如果我取消注释 Thread.sleep(1000)
,程序将永远不会结束,因为 JIT 有足够的时间来优化代码并将 stop
替换为硬编码值,即 false
。
我正在试验 some C# code 的 Java 端口,我很惊讶地看到 javac 1.8.0_60 每次发出一个 getfield
操作码访问了对象字段。
这里是 Java 代码:
public class BigInteger
{
private int[] bits;
private int sign;
//...
public byte[] ToByteArray()
{
if (sign == 0)
{
return new byte[] { 0 };
}
byte highByte;
int nonZeroDwordIndex = 0;
int highDword;
if (bits == null)
{
highByte = (byte)((sign < 0) ? 0xff : 0x00);
highDword = sign;
}
else if (sign == -1)
{
highByte = (byte)0xff;
assert bits.length > 0;
assert bits[bits.length - 1] != 0;
while (bits[nonZeroDwordIndex] == 0)
{
nonZeroDwordIndex++;
}
highDword = ~bits[bits.length - 1];
if (bits.length - 1 == nonZeroDwordIndex)
{
highDword += 1;
}
}
else
{
assert sign == 1;
highByte = 0x00;
highDword = bits[bits.length - 1];
}
byte msb;
int msbIndex;
if ((msb = (byte)(highDword >>> 24)) != highByte)
{
msbIndex = 3;
}
else if ((msb = (byte)(highDword >>> 16)) != highByte)
{
msbIndex = 2;
}
else if ((msb = (byte)(highDword >>> 8)) != highByte)
{
msbIndex = 1;
}
else
{
msb = (byte)highDword;
msbIndex = 0;
}
boolean needExtraByte = (msb & 0x80) != (highByte & 0x80);
byte[] bytes;
int curByte = 0;
if (bits == null)
{
bytes = new byte[msbIndex + 1 + (needExtraByte ? 1 : 0)];
assert bytes.length <= 4;
}
else
{
bytes = new byte[4 * (bits.length - 1) + msbIndex + 1 + (needExtraByte ? 1 : 0)];
for (int i = 0; i < bits.length - 1; i++)
{
int dword = bits[i];
if (sign == -1)
{
dword = ~dword;
if (i <= nonZeroDwordIndex)
{
dword = dword + 1;
}
}
for (int j = 0; j < 4; j++)
{
bytes[curByte++] = (byte)dword;
dword >>>= 8;
}
}
}
for (int j = 0; j <= msbIndex; j++)
{
bytes[curByte++] = (byte)highDword;
highDword >>>= 8;
}
if (needExtraByte)
{
bytes[bytes.length - 1] = highByte;
}
return bytes;
}
}
据 javap 报告,javac 1.8.0_60 生成以下字节码:
public byte[] ToByteArray(); Code: 0: aload_0 1: getfield #3 // Field sign:I 4: ifne 15 7: iconst_1 8: newarray byte 10: dup 11: iconst_0 12: iconst_0 13: bastore 14: areturn 15: iconst_0 16: istore_2 17: aload_0 18: getfield #2 // Field bits:[I 21: ifnonnull 48 24: aload_0 25: getfield #3 // Field sign:I 28: ifge 37 31: sipush 255 34: goto 38 37: iconst_0 38: i2b 39: istore_1 40: aload_0 41: getfield #3 // Field sign:I 44: istore_3 45: goto 193 48: aload_0 49: getfield #3 // Field sign:I 52: iconst_m1 53: if_icmpne 156 56: iconst_m1 57: istore_1 58: getstatic #11 // Field $assertionsDisabled:Z 61: ifne 80 64: aload_0 65: getfield #2 // Field bits:[I 68: arraylength 69: ifgt 80 72: new #12 // class java/lang/AssertionError 75: dup 76: invokespecial #13 // Method java/lang/AssertionError."":()V 79: athrow 80: getstatic #11 // Field $assertionsDisabled:Z 83: ifne 109 86: aload_0 87: getfield #2 // Field bits:[I 90: aload_0 91: getfield #2 // Field bits:[I 94: arraylength 95: iconst_1 96: isub 97: iaload 98: ifne 109 101: new #12 // class java/lang/AssertionError 104: dup 105: invokespecial #13 // Method java/lang/AssertionError."":()V 108: athrow 109: aload_0 110: getfield #2 // Field bits:[I 113: iload_2 114: iaload 115: ifne 124 118: iinc 2, 1 121: goto 109 124: aload_0 125: getfield #2 // Field bits:[I 128: aload_0 129: getfield #2 // Field bits:[I 132: arraylength 133: iconst_1 134: isub 135: iaload 136: iconst_m1 137: ixor 138: istore_3 139: aload_0 140: getfield #2 // Field bits:[I 143: arraylength 144: iconst_1 145: isub 146: iload_2 147: if_icmpne 193 150: iinc 3, 1 153: goto 193 156: getstatic #11 // Field $assertionsDisabled:Z 159: ifne 178 162: aload_0 163: getfield #3 // Field sign:I 166: iconst_1 167: if_icmpeq 178 170: new #12 // class java/lang/AssertionError 173: dup 174: invokespecial #13 // Method java/lang/AssertionError."":()V 177: athrow 178: iconst_0 179: istore_1 180: aload_0 181: getfield #2 // Field bits:[I 184: aload_0 185: getfield #2 // Field bits:[I 188: arraylength 189: iconst_1 190: isub 191: iaload 192: istore_3 193: iload_3 194: bipush 24 196: iushr 197: i2b 198: dup 199: istore 4 201: iload_1 202: if_icmpeq 211 205: iconst_3 206: istore 5 208: goto 254 211: iload_3 212: bipush 16 214: iushr 215: i2b 216: dup 217: istore 4 219: iload_1 220: if_icmpeq 229 223: iconst_2 224: istore 5 226: goto 254 229: iload_3 230: bipush 8 232: iushr 233: i2b 234: dup 235: istore 4 237: iload_1 238: if_icmpeq 247 241: iconst_1 242: istore 5 244: goto 254 247: iload_3 248: i2b 249: istore 4 251: iconst_0 252: istore 5 254: iload 4 256: sipush 128 259: iand 260: iload_1 261: sipush 128 264: iand 265: if_icmpeq 272 268: iconst_1 269: goto 273 272: iconst_0 273: istore 6 275: iconst_0 276: istore 8 278: aload_0 279: getfield #2 // Field bits:[I 282: ifnonnull 325 285: iload 5 287: iconst_1 288: iadd 289: iload 6 291: ifeq 298 294: iconst_1 295: goto 299 298: iconst_0 299: iadd 300: newarray byte 302: astore 7 304: getstatic #11 // Field $assertionsDisabled:Z 307: ifne 443 310: aload 7 312: arraylength 313: iconst_4 314: if_icmple 443 317: new #12 // class java/lang/AssertionError 320: dup 321: invokespecial #13 // Method java/lang/AssertionError."":()V 324: athrow 325: iconst_4 326: aload_0 327: getfield #2 // Field bits:[I 330: arraylength 331: iconst_1 332: isub 333: imul 334: iload 5 336: iadd 337: iconst_1 338: iadd 339: iload 6 341: ifeq 348 344: iconst_1 345: goto 349 348: iconst_0 349: iadd 350: newarray byte 352: astore 7 354: iconst_0 355: istore 9 357: iload 9 359: aload_0 360: getfield #2 // Field bits:[I 363: arraylength 364: iconst_1 365: isub 366: if_icmpge 443 369: aload_0 370: getfield #2 // Field bits:[I 373: iload 9 375: iaload 376: istore 10 378: aload_0 379: getfield #3 // Field sign:I 382: iconst_m1 383: if_icmpne 404 386: iload 10 388: iconst_m1 389: ixor 390: istore 10 392: iload 9 394: iload_2 395: if_icmpgt 404 398: iload 10 400: iconst_1 401: iadd 402: istore 10 404: iconst_0 405: istore 11 407: iload 11 409: iconst_4 410: if_icmpge 437 413: aload 7 415: iload 8 417: iinc 8, 1 420: iload 10 422: i2b 423: bastore 424: iload 10 426: bipush 8 428: iushr 429: istore 10 431: iinc 11, 1 434: goto 407 437: iinc 9, 1 440: goto 357 443: iconst_0 444: istore 9 446: iload 9 448: iload 5 450: if_icmpgt 474 453: aload 7 455: iload 8 457: iinc 8, 1 460: iload_3 461: i2b 462: bastore 463: iload_3 464: bipush 8 466: iushr 467: istore_3 468: iinc 9, 1 471: goto 446 474: iload 6 476: ifeq 488 479: aload 7 481: aload 7 483: arraylength 484: iconst_1 485: isub 486: iload_1 487: bastore 488: aload 7 490: areturn
请注意,每次访问 sign
和 bits
字段时,编译器都会发出 getfield
操作码。
阅读 JLS8 的第 17.4.5 节,发生在订单之前,我不明白为什么每次 sign
和 [=15= 时都需要发出 getfield
操作码] 字段被访问(第一次除外)。
Java 编译器仅发出两个 getfield
操作码并将当时可见的字段值保存在框架局部变量中是否合法?
这不仅是合法的,而且一旦代码被 JIT 编译器编译(表达式提升是可用的优化之一)就可能发生。
例如下面的代码:
public class Test {
private boolean stop;
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
new Thread(t::m).start();
// Thread.sleep(1000);
System.out.println("stop is now true");
t.stop = true;
}
private void m() {
while (!stop);
System.out.println("Finished");
}
}
立即终止(至少在我的机器上)。这不能保证,但因为每次都获取该字段,所以有一个点可以传播和捕获更改。
但是,如果我取消注释 Thread.sleep(1000)
,程序将永远不会结束,因为 JIT 有足够的时间来优化代码并将 stop
替换为硬编码值,即 false
。