我遇到了一个采访问题:
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对齐。
答案 0 :(得分:10)
取决于JVM。至于HotSpot JVM,正确的答案是:
答案 1 :(得分:5)
我会回答这样的采访问题如下:
未指定...
它可能取决于Java实现(即Java版本,供应商和目标指令集/体系结构)以及32位与64位。
该对象由对象标题(其大小特定于平台)的字段(其对齐是特定于平台的)组成,然后将大小向上舍入为特定于平台的堆对象大小粒度。
更好的方法是测量对象大小(例如,在现代HotSpot JVM上使用TLAB)。
虽然对象大小可能会有所不同,但int
和char
的含义却不同。 int
始终为32位有符号,char
始终为16位无符号。
如果面试官告诉你:
对于32位:4 + 4 + 2 + 8 = 18字节
这可能意味着int
和int
以及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版本和设置,下面将描述一个例外。
答案 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字节对齐,但这会浪费更多的内存,而不是在大多数情况下保存的内存。