8

我试图了解 Java 中对象的内存占用量是多少。我阅读了这个文档和其他关于 Java 中对象和内存的文档。

但是,当我使用sizeof Java 库或 visualvm 时,我得到了两个不同的结果,其中没有一个符合我根据之前的参考 ( http://www.javamex.com ) 所期望的结果。

对于我的测试,我正在Java SE 7 Developer Preview使用64-bits Macwith java.sizeof 0.2.1and visualvm 1.3.5

我有三个班TestObject,,,,TestObject2TestObject3

public class TestObject
{

}

public class TestObject2 extends TestObject
{
    int a = 3;
}

public class TestObject3 extends TestObject2
{
    int b = 4;
    int c = 5;
}

我的主要课程:

public class memoryTester
{
    public static void main(String[] args) throws Throwable
    {
        TestObject object1 = new TestObject();
        TestObject2 object2 = new TestObject2();
        TestObject3 object3 = new TestObject3();

        int sum = object2.a + object3.b + object3.c;
        System.out.println(sum);

        SizeOf.turnOnDebug();

        System.out.println(SizeOf.humanReadable(SizeOf.deepSizeOf(object1)));
        System.out.println(SizeOf.humanReadable(SizeOf.deepSizeOf(object2)));
        System.out.println(SizeOf.humanReadable(SizeOf.deepSizeOf(object3)));
    }
}

使用 java.SizeOf() 我得到:

{ test.TestObject
} size = 16.0b
16.0b

{ test.TestObject2
 a = 3
} size = 16.0b
16.0b

{ test.TestObject3
 b = 4
 c = 5
} size = 24.0b
24.0b

使用visualvm我有:

this (Java frame)   TestObject  #1  16
this (Java frame)   TestObject2 #1  20
this (Java frame)   TestObject3 #1  28

根据我通过 Internet 阅读的文档,因为我是 64 位的,所以我应该有一个 16 字节的对象头,对于TestObject.

然后TestObject2我应该为整数字段添加 4 个字节,给出 20 个字节,我应该再次添加 4 个字节的填充,总大小为 24 个字节TestObject2。我错了吗?

继续这种方式TestObject3,我必须为应该提供 32 个字节的两个整数字段再添加 8 个字节。

VisualVm 似乎忽略了填充,而 java.sizeOf 似乎错过了 4 个字节,就好像包含在对象头中一样。我可以用 4 个布尔值替换一个整数,它给出了相同的结果。

问题:

为什么这两个工具给出不同的结果?

我们应该有填充吗?

我还在某处读到(我没有找到链接),在一个类和它的子类之间可能有一些填充,对吗?在那种情况下,继承的类树可能会有一些内存开销?

最后,是否有一些 Java 规范/文档详细说明了 Java 正在做什么?

谢谢你的帮助。

更新:

为了回答 utapyngo 的评论,为了获取 visualvm 中对象的大小,我创建了一个堆转储,然后在“类”部分中,我检查了“实例”列之后的“大小”列。每种对象的实例数(如果为 1)。

为了回答 Nathaniel Ford 的评论,我初始化了每个字段,然后在我的主要方法中对它们进行了简单的求和以利用它们。它没有改变结果。

4

1 回答 1

2

是的,填充可能发生。堆栈上的对象也可以完全优化。只有 JVM 知道任何时间点的确切大小。由于这些从 Java 语言中近似大小的技术都倾向于不同意,但是附加到 JVM 的工具往往是最准确的。我知道在 Java 中实现 sizeOf 的三种主要技术是:

  1. 序列化对象并返回这些字节的长度(显然错误,但对相对比较有用)
  2. 列出项目反射,以及在对象上找到的每个字段的硬编码大小常量。可以调整为有点准确,但 JVM 中的更改和 JVM 可能会或可能不会做的填充会抛出它。
  3. 列表项创建对象负载,运行 gc 并比较 jvm 堆大小的变化

这些技术都不是准确的。

如果您在 Oracle JVM 上运行,则在 v1.5 或之后。然后有一种方法可以直接从 Java 运行时使用的 C 结构中读取对象的大小。对于生产来说不是一个好主意,如果弄错了,你可能会导致 JVM 崩溃。但是,如果您想尝试一下,这里有一篇博文可能会很有趣:http: //highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/

至于 Java 实际在做什么的文档,那就是特定于 JVM、特定于版本和可能特定于配置的文档。每个实现都可以自由地以不同的方式处理对象。即使在完全优化对象的范围内,例如,没有从堆栈中传递出来的对象也是空闲的,不会被分配到堆上。一些 JVM 甚至可以设法将对象完全保留在 CPU 寄存器中。这里不是你的情况,但我将它作为一个例子说明为什么获取 Java 对象的真实大小很棘手。

因此,最好采用任何 sizeOf 值,您只需加一点盐就可以将其视为“指导”测量值。

于 2013-03-19T08:28:18.347 回答