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

请注意,每次访问 signbits 字段时,编译器都会发出 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