在对象大小方面,Dalvik对HotSpot的记忆力是否更大?

Gen*_*yan 26 java android memory-management dalvik

我一直想知道Object在Android上占用了多少内存.有许多与HotSpot JVM相关的资源(如此)告诉空对象占用8个字节,空数组占用12个字节,并且所有对象都与8字节边界对齐.因此,没有额外字段的对象应该占用8个字节,最小的对象至少有一个额外字段--16个字节,一个空数组--16个字节,对吧?

我在这个问题上没有找到关于Dalvik的具体信息,并决定通过测试来弄清楚.运行测试结果令人惊讶.

关于计算方法的几句话.Android的Object.hashCode()实现只返回指向int的对象的指针.(似乎很明显和一般,但[另一个惊喜]事实证明,它不在HotSpot JVM上 - 例如使用HotSpot运行MemTest并查看).所以,我在Dalvik上使用了hashCode()的简单性来计算Android上的对象大小,通过在一行中分配测试类的两个实例,并且分配的空间量应该等于它们的hashCode()的差异值(假设Dalvik在完全随机的地址分配它们没有多大意义).只是为了确保每个测试类连续分配4个对象,这总是提供了hashCode()的相同差异.所以,我相信这种方法的正确性毫无疑问.

以下是测试的源代码:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是结果:

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
Run Code Online (Sandbox Code Playgroud)

总结结果:

  • 8字节边界对齐与HotSpot相同,这是唯一相同的.

  • 普通对象最少16个字节(HotSpot上为8个字节)

  • 显然,一个空对象本身占用12个字节(在HotSpot上为8个字节),并且有4个额外字节的空间,直到对象大小从16个字节"跳转"到24个字节的下一个边界.

  • 空数组最少24个字节(HotSpot上为12个字节)

  • 类似地,数组本身占用20个字节(与HotSpot上的12个字节相比),并且存在4个额外字节的数组数据,直到对象大小从24个字节"跳跃"到32个字节的下一个边界.

附加:(响应路易斯的建议)另一个压力测试表明,即使分配一百万个对象实例,任何两个之间的距离也不会小于16个字节.这证明了对象之间潜在的8字节漏洞绝对是进一步分配的死空间,否则当大约一半的内存被分配给对象时,dalvik肯定会把它们中的一些放入"漏洞"中,压力测试将返回8,而不是16.

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);
}
Run Code Online (Sandbox Code Playgroud)

我是否认为与HotSpot相比,Dalvik的Object需要多达8个字节,阵列多8-12个字节

fad*_*den 12

(是的,这是一个老问题,但结果很有意思,所以我稍微戳了一下.)

Object.clone()方法需要制作对象的完整按位副本.要做到这一点,它需要知道一个对象有多大.如果你看一下dvmCloneObject(),你会看到它对数组使用一种方法,对对象使用不同的方法.

对于数组,它调用dvmArrayObjectSize(),将数组长度乘以元素宽度(1,2,4或8),然后从对象的开头添加数组数据的偏移量.每个对象都有一个8字节的标题; 数组的宽度为4字节,并包含额外的4字节填充,以确保正确对齐64位值.因此,对于5元素阵列short,它将是16 + 5*2.

对于普通对象,它只使用objectSize类对象中的字段.这是由一个相当复杂的函数设置的computeFieldOffsets().该函数确保所有对象引用都是第一个(因此GC在扫描时可以少跳过),然后跟随所有64位字段.为了确保64位字段正确对齐,它可以移动其中一个32位基本字段以填充内容.(如果没有合适的32位字段,则只需要4个字节的填充.)

我应该补充一点:所有字段都是32位,除了longdouble64位.对象引用是32位.

因此,准确地说出非数组对象的大小是很棘手的,但通常你会得到8字节的对象标题,总结其他字段的宽度,然后向上舍入到8字节的下一个倍数 - 最后因为所有对象必须是64位对齐的.

这就是理论.为了在实践中看到它,我将其添加到dvmCloneObject():

ALOGD("class=%s size=%d", clazz->descriptor, clazz->objectSize);
Run Code Online (Sandbox Code Playgroud)

并看到logcat输出如:

D dalvikvm: class=Ljava/util/Locale; size=24
D dalvikvm: class=Ljava/util/Date; size=16
Run Code Online (Sandbox Code Playgroud)

Locale有4个参考字段,Date有一个long字段,因此这些值符合预期.

理想情况下,这正是需要多少空间.但是,对象被分配mspace_calloc(),这会增加另外4个或(有时)8个字节的开销.因此,上述值所需的实际空间为32和24,这与您的实验结果相符.


Jes*_*eke 3

我没有给你答案,但我可以建议你可以在源代码中查看几个地方以获取更多信息。

您可以查看 dalvik/vm/oo/Object.h 中的 DataObject 和 ArrayObject 结构。基于此,看起来一个空对象应该只占用 8 个字节,而一个空数组应该占用 12 个字节。这似乎与您的结果不符,尽管我不确定为什么。

您还可以查看 ClassObject 结构中 objectSize 字段的用法以获得更多见解。快速搜索该字段的用法表明,dalvik/vm/alloc/Alloc.cpp 中的 dvmAllocObject 方法似乎负责为新对象分配内存。