Java中32位和64位系统的对象大小差异

时间:2014-07-19 04:48:10

标签: java object memory memory-management jvm

我遇到了一个采访问题:

class Test {
    int a ; 
    int b;
    char c;
}

此类的内存​​对象将采用多少以及实现时的原因:

a)32位计算机

b)64位计算机

我的答案是:

For 32-bit: 4+4+2+8 = 18 bytes
For 64-bit: 4+4+2+16 = 26 bytes

由于分配了一些额外的内存,在32位系统中为8个字节,在64位系统中为16个字节,此外还有正常的对象大小。

请您对此声明作出一些解释。

P.S。:我也希望与其他来源分享我得到的答案(不能依赖,想要验证):

在32位pc对象占用比其数据成员定义的实际obj大小多8个字节.....并且在64位pc对象占用比其数据成员定义的实际对象大小多16个字节。 ..现在问题出现了它为什么会发生...其背后的原因据我所知是:::在32位pc中前8个字节由JVM保留用于引用类定义,对齐,超类和子空间类等。对于64位,它为这些保留16个字节。有一个公式用于计算对象在创建时需要多少堆空间,并计算为.....。 。 Shallow Heap Size = [对类定义的引用] +超类字段的空间+实例字段的空间+ [alignment]

如何计算对象“本身”需要多少内存?显然有一个公式:

Shallow Heap Size = [对类定义的引用] +超类字段的空间+实例字段的空间+ [alignment]

似乎没有太大帮助,嗯?让我们尝试使用以下示例代码应用公式:

class X {
  int a;
  byte b;
  java.lang.Integer c = new java.lang.Integer();
}
class Y extends X {
  java.util.List d;
  java.util.Date e;
}

现在,我们努力回答的问题是 - Y的实例需要多少浅堆大小?假设我们使用的是32位x86架构,让我们开始计算它:

作为起点 - Y是X的子类,因此它的大小包含来自超类的“东西”。因此,在计算Y的大小之前,我们考虑计算X的浅层大小。

跳转到X上的计算,前8个字节用于引用其类定义。此引用始终存在于所有Java对象中,并由JVM用于定义以下状态的内存布局。它还有三个实例变量 - 一个int,一个Integer和一个byte。这些实例变量需要堆如下:

一个字节就是它应该是的。内存中有1个字节。 我们的32位架构中的int需要4个字节。 对Integer的引用也需要4个字节。注意,在计算保留堆时,我们还应该考虑包含在Integer对象中的原语的大小,但是由于我们在这里计算浅堆,我们在计算中只使用4个字节的引用大小。 那么 - 就是这样吗? X = 8字节的浅堆从引用到类定义+ 1字节(字节)+ 4字节(int)+ 4字节(引用整数)= 17字节?事实上 - 没有。现在发挥作用的是对齐(也称为填充)。这意味着JVM以8个字节的倍数分配内存,因此如果我们要创建X的实例,我们将分配24个字节而不是17个字节。

如果你可以关注我们,直到这里,好,但现在我们试着让事情变得更加复杂。我们不是创建X的实例,而是创建Y的实例。这意味着什么 - 我们可以从引用到类定义和对齐中扣除8个字节。它在第一时间可能不是太明显但是 - 你注意到在计算X的浅层大小时我们没有考虑到它也扩展了java.lang.Object,就像所有类都做的那样,即使你没有明确说明它你的源代码?我们不必考虑超类的头大小,因为JVM足够聪明,可以从类定义本身检查它,而不必一直将它复制到对象头中。

对齐也是如此 - 在创建对象时,您只对齐一次,而不是在超类/子类定义的边界。所以我们可以肯定地说,在创建X的子类时,你只会从实例变量继承9个字节。

最后,我们可以跳转到初始任务并开始计算Y的大小。正如我们所看到的,我们已经丢失了9个字节到超类字段。让我们看看当我们实际构造一个Y实例时会添加什么。

引用其类定义的Y标头占用8个字节。与以前的相同。 Date是对象的引用。 4字节。简单。 List是对集合的引用。再4个字节。不重要的。 因此,除了来自超类的9个字节之外,我们还有来自头部的8个字节,来自两个引用的2×4个字节(列表和日期)。 Y实例的总浅层大小为25个字节,与32对齐。

4 个答案:

答案 0 :(得分:10)

取决于JVM。至于HotSpot JVM,正确的答案是:

  • 32位JVM:24字节 = align8(4字节mark_word + 4字节类引用+ 4 + 4 + 2字节字段数据)
  • 64位JVM -XX:+ UseCompressedOops:24字节 = align8(8字节mark_word + 4字节类引用+ 4 + 4 + 2字节字段数据)
  • 64位JVM -XX:-UseCompressedOops:32字节 = align8(8字节mark_word + 8字节类引用+ 4 + 4 + 2字节字段数据)

答案 1 :(得分:5)

我会回答这样的采访问题如下:

  • 未指定...

  • 它可能取决于Java实现(即Java版本,供应商和目标指令集/体系结构)以及32位与64位。

  • 该对象由对象标题(其大小特定于平台)的字段(其对齐是特定于平台的)组成,然后将大小向上舍入为特定于平台的堆对象大小粒度。

  • 更好的方法是测量对象大小(例如,在现代HotSpot JVM上使用TLAB)。

  • 虽然对象大小可能会有所不同,但intchar的含义却不同。 int始终为32位有符号,char始终为16位无符号。


如果面试官告诉你:

  

对于32位:4 + 4 + 2 + 8 = 18字节

这可能意味着intint以及char加上2个32位的对象标题字。但是,我认为他错了。实际上2应该是4,因为我相信字段通常存储为在32位字边界上对齐的4(或8)个字节

  

对于64位:4 + 4 + 2 + 16 = 26字节

如上所述,但是有2个64位字的对象标题。我再次认为这不正确。

  

由于分配了一些额外的内存,在32位系统中为8个字节,在64位系统中为16个字节,此外还有正常的对象大小。

堆分配的粒度是多个2个单词。

但我要强调,这不一定是正确的。当然,官方JVM规范中没有任何内容要求对象像这样表示。

答案 2 :(得分:2)

这是一个棘手的问题。

如果你考虑变量大小,那么它不会改变。 32位和64位都将为您提供24个字节。但是,64位将在内存上加载更多内存,这将占用更多内存。

有助于记住CPU可以处理的是32和64,而不是可以分配的内存量。

有关参考,请参阅Red Hat的此声明:

<强> 9.1。 32位与64位JVM

  

讨论性能时常提出的一个问题是提供更好的整体性能:32位还是64位JVM?在现代的64位硬件上,由64位操作系统托管的64位JVM似乎应该比32位JVM表现更好。为了尝试提供关于该主题的一些定量数据,使用行业标准工作负载执行测试。所有测试都在同一系统上运行,具有相同的操作系统,JVM版本和设置,下面将描述一个例外。

enter image description here

来源:https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/5/html/Performance_Tuning_Guide/chap-Performance_Tuning_Guide-Java_Virtual_Machine_Tuning.html

答案 3 :(得分:2)

要获得准确的会计,您可以关闭TLAB。仍然存在GC结果导致的风险,但它更少受到打击和遗漏。

public class Main {
    static class Test {
        int a ;
        int b;
        char c;
    }

    public static void main(String sdf[]) throws Exception {
        // warmup
        new Test();
        long start = memoryUsed();
        Object o = new Test();
        long used = memoryUsed() - start;
        System.out.printf("Memory used for %s, was %,d bytes%n", o.getClass(), used);

    }

    public static long memoryUsed() {
        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    }
}

注意:对于大型测试,如果将Eden大小增加到1 GB或更多,它会有所帮助。

Memory used for class Main$Test, was 24 bytes
  

对于64位:4 + 4 + 2 + 16 = 26字节

这是不可能的,因为HotSpot JVM只分配8个字节的倍数。 BTW您可以将Java 8中的最小分配单位增加到16或32个字节。

标题是12个字节,因此大小为

4 + 4 + 2 + 12(标题)+ 2(填充到8字节倍数)= 24

使用-XX:ObjectAlignmentInBytes=16运行,您会看到

Memory used for class Main$Test, was 32 bytes

这提出了一个问题,如果浪费内存,你为什么要这样做呢?

该优点与压缩oops有关,它允许您在64位JVM中使用32位引用。通常压缩的oops可以寻址32 GB,因为它知道所有对象都是8字节对齐,即你可以寻址4 G * 8字节。但是,如果将字节对齐增加到16,则可以使用压缩oops处理4G * 16字节或64 GB。我相信你可以使用32字节对齐,但这会浪费更多的内存,而不是在大多数情况下保存的内存。