5

私はC# コードの Java ポートを試していましたgetfieldが、オブジェクト フィールドにアクセスするたびに javac 1.8.0_60 がオペコードを発行していることに驚きました。

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: アイコンスト_1
       8: 新しい配列バイト
      10: 重複
      11: アイコンスト_0
      12: アイコンスト_0
      13:バストア
      14: 戻る
      15: アイコンスト_0
      16: istore_2
      17: aload_0
      18: getfield #2 // フィールド ビット:[I
      21: ifnonnull 48
      24: アロード_0
      25: getfield #3 // フィールド記号:I
      28: イフゲ37
      31: シプシュ255
      34: 後藤 38
      37: アイコンスト_0
      38:アイツーブ
      39: イストア_1
      40: アロード_0
      41: getfield #3 // フィールド記号:I
      44: イストア_3
      45: 後藤193
      48: アロード_0
      49: getfield #3 // フィールド記号:I
      52: アイコンスト_m1
      53: if_icmpne 156
      56: アイコンスト_m1
      57: イストア_1
      58: getstatic #11 // フィールド $assertionsDisabled:Z
      61: イフネ80
      64: アロード_0
      65: getfield #2 // フィールド ビット:[I
      68: 配列の長さ
      69: イフト 80
      72: 新しい #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: aload_0
      91: getfield #2 // フィールド ビット:[I
      94: 配列の長さ
      95: アイコンスト_1
      96:イサブ
      97:イアロード
      98: イフネ109
     101: 新しい #12 // クラス java/lang/AssertionError
     104: デュプ
     105: invokespecial #13 // メソッド java/lang/AssertionError."":()V
     108: 投げる
     109: aload_0
     110: getfield #2 // フィールド ビット:[I
     113: iload_2
     114: イアロード
     115: イフネ124
     118: イインク 2, 1
     121: 後藤109
     124: アロード_0
     125: getfield #2 // フィールド ビット:[I
     128: アロード_0
     129: getfield #2 // フィールド ビット:[I
     132: 配列の長さ
     133: アイコンスト_1
     134: イサブ
     135: イアロード
     136: アイコンスト_m1
     137:イクサー
     138: イストア_3
     139: アロード_0
     140: getfield #2 // フィールド ビット:[I
     143: 配列の長さ
     144: アイコンスト_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: アイコンスト_1
     167: if_icmpeq 178
     170: 新しい #12 // クラス java/lang/AssertionError
     173: デュプ
     174: invokespecial #13 // メソッド java/lang/AssertionError."":()V
     177: 投げる
     178: アイコンスト_0
     179: イストア_1
     180: aload_0
     181: getfield #2 // フィールド ビット:[I
     184: アロード_0
     185: getfield #2 // フィールド ビット:[I
     188: 配列の長さ
     189: アイコンスト_1
     190: イサブ
     191: イアロード
     192: イストア_3
     193: iload_3
     194:バイプッシュ24
     196: イシュル
     197: えんとつ
     198: デュプ
     199: イストア4
     201: iload_1
     202: if_icmpeq 211
     205: アイコンスト_3
     206: ストア 5
     208: 後藤 254
     211: iload_3
     212:バイプッシュ16
     214: ウシュル
     215:アイツーブ
     216: デュプ
     217: ストア 4
     219: iload_1
     220: if_icmpeq 229
     223: アイコンスト_2
     224: イストア5
     226: 後藤 254
     229: iload_3
     230:バイプッシュ8
     232: ウシュル
     233: えんとつ
     234: デュプ
     235: ストア 4
     237: iload_1
     238: if_icmpeq 247
     241: アイコンスト_1
     242: ストア5
     244: 後藤 254
     247: iload_3
     248: えんとつ
     249: イストア4
     251: アイコンスト_0
     252: ストア5
     254: iload 4
     256: シプシュ128
     259: イアンド
     260: iload_1
     261: シプシュ128
     264: イアンド
     265: if_icmpeq 272
     268: アイコンスト_1
     269: 後藤 273
     272: アイコンスト_0
     273: イストア6
     275: アイコンスト_0
     276: イストア8
     278: アロード_0
     279: getfield #2 // フィールド ビット:[I
     282: 325
     285: イロード5
     287: アイコンスト_1
     288: しんしん
     289: イロード6
     291: イフェク 298
     294: アイコンスト_1
     295: 後藤 299
     298: アイコンスト_0
     299: たつ
     300: newarray バイト
     302: アストア 7
     304: getstatic #11 // フィールド $assertionsDisabled:Z
     307: イフネ443
     310: アロード 7
     312: 配列の長さ
     313: アイコンスト_4
     314: if_icmple 443
     317: 新しい #12 // クラス java/lang/AssertionError
     320: デュプ
     321: invokespecial #13 // メソッド java/lang/AssertionError."":()V
     324: 投げる
     325: アイコンスト_4
     326: アロード_0
     327: getfield #2 // フィールド ビット:[I
     330: 配列の長さ
     331: アイコンスト_1
     332: イサブ
     333: イムル
     334: iload 5
     336: しんしん
     337: アイコンスト_1
     338: しんしん
     339: イロード6
     341: イフェク 348
     344: アイコンスト_1
     345: 後藤 349
     348: アイコンスト_0
     349: しんしん
     350: newarray バイト
     352:アストア7
     354: アイコンスト_0
     355: イストア9
     357: イロード9
     359: アロード_0
     360: getfield #2 // フィールド ビット:[I
     363: 配列の長さ
     364: アイコンスト_1
     365: イサブ
     366: if_icmpge 443
     369: アロード_0
     370: getfield #2 // フィールド ビット:[I
     373:イロード9
     375: イアロード
     376: イストア10
     378: アロード_0
     379: getfield #3 // フィールド記号:I
     382: アイコンスト_m1
     383: if_icmpne 404
     386: イロード10
     388: アイコンスト_m1
     389:イクサー
     390: イストア 10
     392:イロード9
     394: イロード_2
     395: if_icmpgt 404
     398:イロード10
     400: アイコンスト_1
     401: iadd
     402: イストア 10
     404: アイコンスト_0
     405: イストア 11
     407: iload 11
     409: アイコンスト_4
     410: if_icmpge 437
     413: アロード 7
     415: iload 8
     417: イインク 8, 1
     420: iload 10
     422: えんとつ
     423: バストア
     424: iload 10
     426:バイプッシュ8
     428: ウシュル
     429: ストア 10
     431: イインク 11, 1
     434: 407に行く
     437: イインク 9, 1
     440: 後藤 357
     443: アイコンスト_0
     444: イストア9
     446: イロード9
     448: イロード5
     450: if_icmpgt 474
     453: アロード 7
     455: iload 8
     457: イインク 8, 1
     460: iload_3
     461: しんしん
     462:バストア
     463: iload_3
     464:バイプッシュ8
     466: イシュル
     467: イストア_3
     468: イインク 9, 1
     471: 後藤446
     474: イロード6
     476: 488
     479: アロード 7
     481: アロード 7
     483: 配列の長さ
     484: アイコンスト_1
     485: イサブ
     486: iload_1
     487:バストア
     488: アロード 7
     490:リターン

およびフィールドがアクセスされるgetfieldたびに、コンパイラによってオペコードが発行されたことに注意してください。signbits

JLS8 の §17.4.5、Happens-before Order を読んで、フィールドとフィールドがアクセスされるgetfieldたびにオペコードを発行する必要がある理由がわかりません(初回以外)。signbits

Java コンパイラが 2 つのgetfieldオペコードのみを発行し、その時点で表示されているフィールドの値をフレーム ローカル変数に保存することは合法でしょうか?

4

1 に答える 1

6

これは合法であるだけでなく、コードが JIT コンパイラーによってコンパイルされると発生する可能性があります (式ホイストは利用可能な最適化の 1 つです)。

たとえば、以下のコード:

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");
  }

}

すぐに終了します(少なくとも私のマシンでは)。これは保証されていませんが、フィールドは毎回フェッチされるため、変更が伝播されキャッチされるポイントがあります。

ただし、コメントを外すと、JIT にはコードを最適化し、ハードコードされた値にThread.sleep(1000)置き換えるのに十分な時間があるため、プログラムは終了しません。stopfalse

于 2015-10-07T16:04:27.527 に答える