我一直想知道一个对象在 Android 上占用了多少内存。 有许多与 HotSpot JVM 相关的资源(如 this )告诉我们一个空对象需要 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;
}
}
结果如下:
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 个额外字节的数组数据空间。
补充:(回应路易斯的建议) 另一项压力测试表明,即使分配一百万个 Object 实例,任何两个实例之间的距离也绝不会小于 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);
}
与 HotSpot 相比,Dalvik 的 Object 最多占用 8 个字节 和 array 最多占用 8-12 个字节,我是否认为正确?
最佳答案
(是的,这是一个老问题,但结果有点有趣,所以我稍微戳了一下。)
Object.clone() 方法需要对对象进行完整的按位复制。为此,它需要知道一个对象有多大。如果您查看 dvmCloneObject(),您会发现它对数组使用一种方法,而对对象使用另一种方法。
对于数组,它调用dvmArrayObjectSize(),它将数组长度乘以元素宽度(1、2、4或8),然后加上数组数据的偏移量对象的开始。每个对象都有一个 8 字节的 header ;数组有 4 字节宽度,并包含额外的 4 字节填充以确保 64 位值正确对齐。因此,对于 short 的 5 元素数组,它将是 16 + 5 * 2。
对于普通对象,它只是使用类对象中的objectSize字段。这是由一个名为 computeFieldOffsets() 的相当复杂的函数设置的。该函数确保所有对象引用首先出现(因此 GC 在扫描时可以少跳过),然后是所有 64 位字段。为确保 64 位字段正确对齐,它可能会向上移动 32 位原始字段之一以填充内容。 (如果没有合适的 32 位字段,您只会得到 4 个字节的填充。)
我应该补充一点:所有字段都是 32 位的,除了 long 和 double 是 64 位的。对象引用是 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 有一个 long 字段,因此这些值符合预期。
理想情况下,这正是所需的空间。但是,该对象是使用 mspace_calloc() 分配的,这会增加另外 4 或(有时)8 字节的开销。因此,上述值所需的 实际 空间将是 32 和 24,这与您的实验结果相符。
关于java - 就对象大小而言,Dalvik 是否比 HotSpot 更需要内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10824677/
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife
我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只