就对象大小而言,Dalvik是否比HotSpot更耗费内存?

我一直在想,一个对象在Android上占用了多less内存。 与HotSpot JVM相关的大量资源( 如此 )告诉一个空对象需要8个字节,而一个空数组需要12个字节,并且所有对象都alignment到8个字节边界。 因此没有额外字段的对象应该占用8个字节,最小的对象至less有一个额外的字段–16字节,一个空的数组–16字节,对吧?

我没有find有关Dalvik的具体信息,并决定通过testing。 运行testing有令人惊讶的结果

关于计算方法的几句话。 Android的Object.hashCode()的实现只是返回指向被转换为int的对象的指针。 (看起来很明显,一般,但[另一个惊喜],事实certificate,它不在HotSpot JVM上 – 例如运行MemTest和HotSpot)。 所以我在Dalvik上使用了hashCode()的简单性,通过在一行中分配被testing类的两个实例来计算Android上的对象大小,分配的空间量应该等于它们的hashCode()值(假设Dalvik在完全随机的地址上分配这些值是没有意义的)。 只是为了确保每个testing类中总是分配了4个对象,总是和hashCode()相同。 所以我相信这个方法的正确性是没有疑问的。

这里是testing的源代码:

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字节的边界alignment与HotSpot相同,这是唯一相同的。

  • 一个普通对象的最小16字节(HotSpot vs 8)

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

  • 最less24个字节为一个空的arrays(12热点上)

  • 类似地,一个数组本身占用20个字节(在HotSpot上为12个字节),并且有足够的空间存储4个额外字节的数组数据,直到对象大小从24字节跳到下一个32字节的边界。

附加:(回应路易的build议)另一个压力testing显示,即使分配一百万个对象实例,任何两个之间的距离也不会小于16个字节。 这就是certificate对象之间潜在的8字节漏洞肯定是进一步分配的死空间,否则当大约一半的内存被分配给对象的时候,dalvik肯定会把它们中的一些放入“漏洞”中,压力testing将返回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); } 

Dalvik的对象比HotSpot 占8个字节数组多8-12个字节吗?

Solutions Collecting From Web of "就对象大小而言,Dalvik是否比HotSpot更耗费内存?"

(是的,这是一个古老的问题,但结果有趣,所以我捅了一下。)

Object.clone()方法需要对对象进行完整的按位拷贝。 要做到这一点,它需要知道一个物体有多大。 如果你看dvmCloneObject() ,你会发现它使用一个数组的方法和一个不同的对象方法。

对于数组,它调用dvmArrayObjectSize() ,它将数组长度乘以元素宽度(1,2,4或8),然后从对象的开始处添加数组数据的偏移量。 每个对象都有一个8字节的头文件; 数组有4个字节的宽度,并包含额外的4个填充字节,以确保64位值正确alignment。 所以对于5元素的short阵来说,它将是16 + 5 * 2。

对于普通对象,它只是使用类对象中的objectSize字段。 这是由一个称为computeFieldOffsets()的相当复杂的函数设置的。 该function可以确保所有的对象引用最先出现(所以GC可以在扫描时跳过),然后跟随所有的64位字段。 为了确保64位字段正确alignment,可以将32位原始字段之一移动以填充。 (如果没有合适的32位字段,只需填充4个字节)

我应该补充说:所有的字段都是32位,除了longdouble ,都是64位。 对象引用是32位的。

所以说一个非数组对象的大小是非常棘手的,但是一般情况下,你需要8个字节的对象头,总结其他字段的宽度,然后舍入8个字节的下一个倍数 – 最后因为所有对象必须是64位alignment的。

这就是理论。 为了在实践中看到它,我将其添加到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 

区域设置有4个参考字段,date有一个long字段,所以这些值符合预期。

理想情况下,这就是需要多less空间。 但是,该对象被分配了mspace_calloc() ,这会添加另外4个或(有时)8个字节的开销。 所以上述值所需的实际空间将是32和24,这与您的实验结果相符。

我没有你的答案,但我可以build议一些你可能会在源代码中find更多信息的地方。

你可以看看dalvik / vm / oo / Object.h中的DataObject和ArrayObject结构。 基于此,看起来像一个空对象应该只需要8个字节,而一个空数组应该需要12个字节。 这似乎不符合你的结果,虽然我不知道为什么。

您还可以查看ClassObject结构中objectSize字段的用法,以获取更多信息。 快速search该字段的用法,显示dalvik / vm / alloc / Alloc.cpp中的dvmAllocObject方法似乎负责为新对象分配内存。