5

我正在试验一些 C# 代码的 Java 端口,我惊讶地发现 javac 1.8.0_60getfield每次访问对象字段时都会发出一个操作码。

这是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 产生以下字节码:

  公共字节[] ToByteArray();
    代码:
       0:aload_0
       1: getfield #3 // 字段符号:I
       4:如果 15
       7:iconst_1
       8:新数组字节
      10:重复
      11:iconst_0
      12:iconst_0
      13:巴斯托
      14:返回
      15:iconst_0
      16:istore_2
      17:加载_0
      18: getfield #2 // 字段位:[I
      21:如果非空 48
      24:加载_0
      25: getfield #3 // 字段符号:I
      28:如果 37
      31:啜饮 255
      34:转到 38
      37:图标st_0
      38:i2b
      39:istore_1
      40:加载_0
      41: getfield #3 // 字段符号:I
      44:istore_3
      45:转到 193
      48:加载_0
      49: getfield #3 // 字段符号:I
      52:图标st_m1
      53: if_icmpne 156
      56:图标st_m1
      57:istore_1
      58: getstatic #11 // 字段 $assertionsDisabled:Z
      61:如果 80
      64:加载_0
      65: getfield #2 // 字段位:[I
      68:数组长度
      69:如果 80
      72: new #12 // 类 java/lang/AssertionError
      75:重复
      76: invokespecial #13 // 方法 java/lang/AssertionError."":()V
      79:扔
      80: getstatic #11 // 字段 $assertionsDisabled:Z
      83:如果 109
      86:加载_0
      87: getfield #2 // 字段位:[I
      90:加载_0
      91: getfield #2 // 字段位:[I
      94:数组长度
      95:图标st_1
      96:伊苏
      97:加载
      98:如果 109
     101: new #12 // 类 java/lang/AssertionError
     104:重复
     105: invokespecial #13 // 方法 java/lang/AssertionError."":()V
     108:扔
     109:加载_0
     110: getfield #2 // 字段位:[I
     113:加载_2
     114:加载
     115:如果 124
     118:iinc 2、1
     121:转到109
     124:加载_0
     125: getfield #2 // 字段位:[I
     128:加载_0
     129: getfield #2 // 字段位:[I
     132:数组长度
     133:图标st_1
     第134话
     135:加载
     136:图标st_m1
     第137话
     138:istore_3
     139:加载_0
     140: getfield #2 // 字段位:[I
     143:数组长度
     144:图标st_1
     145:伊苏
     146:加载_2
     147: if_icmpne 193
     150:iinc 3、1
     153:转到193
     156: getstatic #11 // 字段 $assertionsDisabled:Z
     159:如果 178
     162:加载_0
     163: getfield #3 // 字段符号:I
     166:图标st_1
     167: if_icmpeq 178
     170: new #12 // 类 java/lang/AssertionError
     173:重复
     174: invokespecial #13 // 方法 java/lang/AssertionError."":()V
     177:扔
     178:图标st_0
     179:istore_1
     180:加载_0
     181: getfield #2 // 字段位:[I
     184:加载_0
     185: getfield #2 // 字段位:[I
     188:数组长度
     189:图标st_1
     190:伊苏
     191:加载
     192:istore_3
     193:iload_3
     194:双推 24
     第196话
     197:i2b
     198:重复
     199:istore 4
     201:iload_1
     202:if_icmpeq 211
     205:图标st_3
     206:istore 5
     208:转到254
     211:加载_3
     212:双推 16
     第214话
     215:i2b
     216:重复
     217:istore 4
     219:iload_1
     220:if_icmpeq 229
     223:图标st_2
     224:istore 5
     226:转到254
     229:iload_3
     230:双向推 8
     第232话
     233:i2b
     234:重复
     235:istore 4
     237:iload_1
     238:如果_icmpeq 247
     241:图标st_1
     242:istore 5
     244:转到254
     247:iload_3
     248:i2b
     249:istore 4
     251:图标st_0
     252:istore 5
     254:加载4
     256:吸128
     第259话
     260:iload_1
     261:吸128
     第264话
     265:如果_icmpeq 272
     268:图标st_1
     269:转到273
     272:图标st_0
     273:istore 6
     275:图标st_0
     276:istore 8
     278:加载_0
     279: getfield #2 // 字段位:[I
     282:如果非空 325
     285:加载5
     287:图标st_1
     288:我加
     289:加载6
     291:ifeq 298
     294:图标st_1
     295:转到299
     298:图标st_0
     299:我加
     300:新数组字节
     302:商店 7
     304: getstatic #11 // 字段 $assertionsDisabled:Z
     307:如果 443
     310:加载 7
     312:数组长度
     313:图标st_4
     第314章 443
     317: 新 #12 // 类 java/lang/AssertionError
     320:重复
     321: invokespecial #13 // 方法 java/lang/AssertionError."":()V
     324:扔
     325:图标st_4
     326:加载_0
     327: getfield #2 // 字段位:[I
     330:数组长度
     331:图标st_1
     第332话
     第333话
     334:加载 5
     336:我加
     337:图标st_1
     第338话
     339:加载6
     341:ifeq 348
     344:图标st_1
     345:转到349
     348:图标st_0
     349:我加
     350:新数组字节
     352:商店 7
     354:图标st_0
     355:istore 9
     357:加载 9
     359:加载_0
     360: getfield #2 // 字段位:[I
     363:数组长度
     364:图标st_1
     365:伊苏
     第366章 443
     369:加载_0
     370: getfield #2 // 字段位:[I
     373:加载 9
     第375话
     376:istore 10
     378:加载_0
     379: getfield #3 // 字段符号:I
     382:图标st_m1
     383:if_icmpne 404
     386:加载 10
     388:图标st_m1
     第389话
     390:istore 10
     392:加载 9
     394:iload_2
     395:如果_icmpgt 404
     398:加载 10
     400:图标st_1
     401:添加
     402:istore 10
     404:图标st_0
     405:istore 11
     407:加载 11
     409:图标st_4
     410:如果_icmpge 437
     413:加载 7
     415:加载 8
     417:iinc 8、1
     420:加载 10
     422:i2b
     423:巴斯托
     424:加载 10
     426:双向推 8
     第428话
     429:istore 10
     431:iinc 11、1
     434:转到407
     437:iinc 9、1
     440:转到 357
     443:图标st_0
     444:istore 9
     446:加载 9
     448:加载 5
     450: if_icmpgt 474
     453:加载 7
     455:加载 8
     457:iinc 8、1
     460:iload_3
     461:i2b
     462:巴斯托
     463:iload_3
     464:双向推 8
     第466话
     467:istore_3
     468:iinc 9、1
     471:转到446
     474:加载6
     476:ifeq 488
     479:加载 7
     481:加载 7
     483:数组长度
     484:图标st_1
     第485话
     486:iload_1
     487:巴斯托
     488:加载 7
     490:返回

请注意,getfield每次访问signandbits字段时,编译器都会发出一个操作码。

阅读 JLS8 的§17.4.5, Happens-before Order, 我不明白为什么getfield每次访问signandbits字段时都需要发出操作码(第一次除外)。

Java 编译器只发出两个getfield操作码并将当时可见的字段值保存在帧局部变量中是否合法?

4

1 回答 1

6

它不仅是合法的,而且一旦代码被 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.

于 2015-10-07T16:04:27.527 回答