26

オブジェクトが Android でどのくらいのメモリを占有するのか疑問に思っていました。空のオブジェクトは 8 バイト、空の配列は 12 バイトであり、すべてのオブジェクトが 8 バイト境界に整列されていることを伝える HotSpot JVM に関連する(このような) リソースは多数あります。したがって、余分なフィールドを持たないオブジェクトは 8 バイト、少なくとも 1 つの余分なフィールドを持つ最小のオブジェクトは 16 バイト、空の配列は 16 バイトである必要があります。

この件に関して、Dalvik に関する具体的な情報が見つからなかったので、テストして把握することにしました。テストを実行すると、驚くべき結果が得られました。

計算方法について一言。Android の Object.hashCode() の実装は、単に int にキャストされたオブジェクトへのポインタを返します。(明らかで一般的なように見えましたが、[別の驚き] たとえば、HotSpot JVM では実行されないことが判明しました。HotSpot で MemTest を実行して確認してください)。そのため、Dalvik で hashCode() のシンプルさを使用して、テスト対象のクラスの 2 つのインスタンスを連続して割り当てることで Android のオブジェクト サイズを計算しました。値 (Dalvik がそれらを完全にランダムなアドレスに割り当てることはほとんど意味がないと仮定します)。念のため、テスト クラスごとに常に 4 つのオブジェクトを 1 行に割り当てました。したがって、この方法の正しさについてはほとんど疑いがないと思います。

テストのソースコードは次のとおりです。

public class MemTest {
    public static void run() {
        Object o1 = new Object();
        Object o2 = new Object();
        Object o3 = new Object();
        Object o4 = new Object();

        EmptyObject eo1 = new EmptyObject();
        EmptyObject eo2 = new EmptyObject();
        EmptyObject eo3 = new EmptyObject();
        EmptyObject eo4 = new EmptyObject();

        ObjectWithBoolean ob1 = new ObjectWithBoolean();
        ObjectWithBoolean ob2 = new ObjectWithBoolean();
        ObjectWithBoolean ob3 = new ObjectWithBoolean();
        ObjectWithBoolean ob4 = new ObjectWithBoolean();

        ObjectWithBooleanAndInt obi1 = new ObjectWithBooleanAndInt();
        ObjectWithBooleanAndInt obi2 = new ObjectWithBooleanAndInt();
        ObjectWithBooleanAndInt obi3 = new ObjectWithBooleanAndInt();
        ObjectWithBooleanAndInt obi4 = new ObjectWithBooleanAndInt();

        ObjectWithLong ol1 = new ObjectWithLong();
        ObjectWithLong ol2 = new ObjectWithLong();
        ObjectWithLong ol3 = new ObjectWithLong();
        ObjectWithLong ol4 = new ObjectWithLong();

        ObjectWith4Ints o4i1 = new ObjectWith4Ints();
        ObjectWith4Ints o4i2 = new ObjectWith4Ints();
        ObjectWith4Ints o4i3 = new ObjectWith4Ints();
        ObjectWith4Ints o4i4 = new ObjectWith4Ints();

        ObjectWith4IntsAndByte o4ib1 = new ObjectWith4IntsAndByte();
        ObjectWith4IntsAndByte o4ib2 = new ObjectWith4IntsAndByte();
        ObjectWith4IntsAndByte o4ib3 = new ObjectWith4IntsAndByte();
        ObjectWith4IntsAndByte o4ib4 = new ObjectWith4IntsAndByte();

        ObjectWith5Ints o5i1 = new ObjectWith5Ints();
        ObjectWith5Ints o5i2 = new ObjectWith5Ints();
        ObjectWith5Ints o5i3 = new ObjectWith5Ints();
        ObjectWith5Ints o5i4 = new ObjectWith5Ints();

        ObjectWithArrayRef oar1 = new ObjectWithArrayRef();
        ObjectWithArrayRef oar2 = new ObjectWithArrayRef();
        ObjectWithArrayRef oar3 = new ObjectWithArrayRef();
        ObjectWithArrayRef oar4 = new ObjectWithArrayRef();

        byte[] a0b1 = new byte[0];
        byte[] a0b2 = new byte[0];
        byte[] a0b3 = new byte[0];
        byte[] a0b4 = new byte[0];

        byte[] a1b1 = new byte[1];
        byte[] a1b2 = new byte[1];
        byte[] a1b3 = new byte[1];
        byte[] a1b4 = new byte[1];

        byte[] a5b1 = new byte[5];
        byte[] a5b2 = new byte[5];
        byte[] a5b3 = new byte[5];
        byte[] a5b4 = new byte[5];

        byte[] a9b1 = new byte[9];
        byte[] a9b2 = new byte[9];
        byte[] a9b3 = new byte[9];
        byte[] a9b4 = new byte[9];

        byte[] a12b1 = new byte[12];
        byte[] a12b2 = new byte[12];
        byte[] a12b3 = new byte[12];
        byte[] a12b4 = new byte[12];

        byte[] a13b1 = new byte[13];
        byte[] a13b2 = new byte[13];
        byte[] a13b3 = new byte[13];
        byte[] a13b4 = new byte[13];

        print("java.lang.Object", o1, o2, o3, o4);
        print("Empty object", eo1, eo2, eo3, eo4);
        print("Object with boolean", ob1, ob2, ob3, ob4);
        print("Object with boolean and int", obi1, obi2, obi3, obi4);
        print("Object with long", ol1, ol2, ol3, ol4);
        print("Object with 4 ints", o4i1, o4i2, o4i3, o4i4);
        print("Object with 4 ints and byte", o4ib1, o4ib2, o4ib3, o4ib4);
        print("Object with 5 ints", o5i1, o5i2, o5i3, o5i4);

        print("Object with array ref", new Object[]{oar1, oar2, oar3, oar4});

        print("new byte[0]", a0b1, a0b2, a0b3, a0b4);
        print("new byte[1]", a1b1, a1b2, a1b3, a1b4);
        print("new byte[5]", a5b1, a5b2, a5b3, a5b4);
        print("new byte[9]", a9b1, a9b2, a9b3, a9b4);
        print("new byte[12]", a12b1, a12b2, a12b3, a12b4);
        print("new byte[13]", a13b1, a13b2, a13b3, a13b4);
    }

    static void print(String title, Object... objects) {
        StringBuilder buf = new StringBuilder(title).append(":");
        int prevHash = objects[0].hashCode();
        int prevDiff = -1;
        for (int i = 1; i < objects.length; i++) {
            int hash = objects[i].hashCode();
            int diff = Math.abs(hash - prevHash);
            if (prevDiff == -1 || prevDiff != diff) {
                buf.append(' ').append(diff);
            }
            prevDiff = diff;
            prevHash = hash;
        }
        System.out.println(buf.toString());
    }

    /******** Test classes ******/

    public static class EmptyObject {
    }

    public static class ObjectWith4Ints {
        int i1;
        int i2;
        int i3;
        int i4;
    }

    public static class ObjectWith4IntsAndByte {
        int i1;
        int i2;
        int i3;
        int i4;
        byte b;
    }

    public static class ObjectWith5Ints {
        int i1;
        int i2;
        int i3;
        int i4;
        int i5;
    }

    public static class ObjectWithArrayRef {
        byte[] b;
    }

    public static class ObjectWithBoolean {
        boolean b;
    }

    public static class ObjectWithBooleanAndInt {
        boolean b;
        int i;
    }

    public static class ObjectWithLong {
        long l;
    }
}

結果は次のとおりです。

java.lang.Object: 16
Empty object: 16
Object with boolean: 16
Object with boolean and int: 24
Object with long: 24
Object with 4 ints: 32
Object with 4 ints and byte: 32
Object with 5 ints: 32
Object with array ref: 16
new byte[0]: 24
new byte[1]: 24
new byte[5]: 32
new byte[9]: 32
new byte[12]: 32
new byte[13]: 40

結果を要約するには:

  • 8 バイト境界のアラインメントは HotSpot と同じで、それ以外は同じです。

  • プレーン オブジェクトの場合は最低 16 バイト (対 HotSpot では 8 バイト)

  • どうやら空のオブジェクト自体が 12 バイトを占有し (HotSpot では 8 バイト)、オブジェクトのサイズが 16 バイトから次の境界の 24 バイトに「ジャンプ」するまで、余分に 4 バイトの余地があります。

  • 空の配列の場合は最低 24 バイト (HotSpot では 12 バイト)

  • 同様に、配列自体は 20 バイト (HotSpot では 12 バイト) を占有し、オブジェクト サイズが 24 バイトから 32 バイトの次の境界に「ジャンプ」するまで、配列データの追加の 4 バイトの余地があります。

追加: (Louis の提案に応えて) 別のストレス テストでは、100 万の Object インスタンスを割り当てても、2 つの間の距離が 16 バイト未満になることは決してないことが示されています。これは、オブジェクト間の潜在的な 8 バイトの穴が、さらなる割り当てのためのデッド スペースであることを証明しています。そうでなければ、メモリの約半分がオブジェクトに割り当てられるまでに、dalvik はそれらの一部を確実に「穴」にも入れているはずです。ストレス テストは 16 ではなく 8 を返します。

public static void run2() {
    int count = 1024 * 1024;
    Object[] arr = new Object[count];
    for (int i = 0; i < count; i++) {
        arr[i] = new Object();
    }
    int[] hashes = new int[count];
    for (int i = 0; i < count; i++) {
        hashes[i] = arr[i].hashCode();
    }
    Arrays.sort(hashes);

    int minDist = Integer.MAX_VALUE;
    for (int i = 1; i < count; i++) {
        int dist = Math.abs(hashes[i] - hashes[i - 1]);
        if (dist < minDist) {
            minDist = dist;
        }
    }
    System.out.println("Allocated "+ count + " Objects, minimum distance is "+ minDist);
}

HotSpot と比較して、Dalvik のオブジェクトが最大 8 バイト多く配列が 8 ~ 12 バイト多いというのは正しいと思いますか?

4

2 に答える 2

13

(はい、これは古い質問ですが、結果は興味深いものだったので、少し調べてみました。)

このObject.clone()メソッドは、オブジェクトの完全なビット単位のコピーを作成する必要があります。そのためには、オブジェクトの大きさを知る必要があります。を見るとdvmCloneObject()、配列に対して 1 つのメソッドを使用し、オブジェクトに対して別のメソッドを使用していることがわかります。

dvmArrayObjectSize()配列の場合は、配列の長さに要素の幅 (1、2、4、または 8) を掛けた を呼び出し、オブジェクトの先頭からの配列データのオフセットを追加します。すべてのオブジェクトには 8 バイトのヘッダーがあります。配列の幅は 4 バイトで、64 ビット値が適切に配置されるように、追加の 4 バイトのパディングが含まれます。したがって、 の 5 要素配列のshort場合、16 + 5 * 2 になります。

通常のオブジェクトの場合objectSize、クラス オブジェクトのフィールドを使用するだけです。これは、 と呼ばれるかなり複雑な関数によって設定されcomputeFieldOffsets()ます。この関数は、すべてのオブジェクト参照が最初に来ることを保証し (そのため、GC はスキャン時にスキップすることが少なくなります)、その後にすべての 64 ビット フィールドが続きます。64 ビット フィールドが適切に配置されるようにするために、32 ビット プリミティブ フィールドの 1 つを上に移動してパディング アウトする場合があります。(適切な 32 ビット フィールドがない場合は、4 バイトのパディングが得られます。)

追加する必要があります。64ビットの と を除いlongて、すべてのフィールドは 32ビットです。doubleオブジェクト参照は 32 ビットです。

そのため、非配列オブジェクトがどのくらいの大きさになるかを正確に言うのは難しいですが、一般的には、8 バイトのオブジェクト ヘッダーを取得し、追加フィールドの幅を合計して、次の 8 バイトの倍数に切り上げます。すべてのオブジェクトが 64 ビットでアラインされている必要があるためです。

それが理論です。実際にそれを見るために、これをに追加しましたdvmCloneObject()

ALOGD("class=%s size=%d", clazz->descriptor, clazz->objectSize);

次のようなlogcat出力が表示されました。

D dalvikvm: class=Ljava/util/Locale; size=24
D dalvikvm: class=Ljava/util/Date; size=16

Locale には 4 つの参照フィールドがあり、Date には 1 つのlongフィールドがあるため、これらの値は予想と一致します。

理想的には、それがまさに必要なスペースです。ただし、オブジェクトは で割り当てられmspace_calloc()、さらに 4 バイトまたは (場合によっては) 8 バイトのオーバーヘッドが追加されます。したがって、上記の値に必要な実際のスペースは 32 と 24 になり、実験結果と一致します。

于 2013-05-09T21:32:22.203 に答える
3

答えはありませんが、ソースで詳細を確認できる場所をいくつか提案できます。

dalvik/vm/oo/Object.h で DataObject および ArrayObject 構造を確認できます。これに基づいて、空のオブジェクトは 8 バイトしかとらないように思われますが、空の配列は 12 バイトを取る必要があります。理由はわかりませんが、これはあなたの結果と一致していないようです。

さらに理解を深めるために、ClassObject 構造体の objectSize フィールドの使用法を調べることもできます。そのフィールドの使用法を簡単に検索すると、dalvik/vm/alloc/Alloc.cpp の dvmAllocObject メソッドが、新しいオブジェクトのメモリ割り当てを担当しているようです。

于 2012-05-30T22:39:20.827 に答える