Java对象的大小由什么决定?

44

什么影响了单个对象在内存中的大小?

我知道原始类型和引用会影响大小,但还有其他因素吗?方法数量和长度是否会影响大小?


使用jmap命令,您会看到是哪些字段做出了贡献。方法的数量和长度是否很重要?只有被覆盖的方法才可能在虚拟表中占用空间。 - bestsss
4个回答

67
这完全取决于实现,但有一些因素会影响Java对象的大小。首先,Java对象中字段的数量和类型肯定会影响空间使用,因为您需要至少有足够的存储空间来容纳所有对象的字段。然而,由于填充、对齐和指针压缩优化,没有直接的公式可以用来精确计算以这种方式使用了多少空间。
至于方法,通常情况下对象中的方法数量对其大小没有影响。方法通常使用称为虚函数表(或“vtable”)的特性实现,使得可以通过基类引用在恒定时间内调用方法。这些表通常通过在多个对象之间共享单个vtable实例,然后使每个对象存储单个指向vtable的指针来存储。
接口方法会稍微复杂一些,因为有几种不同的实现可能性。一种实现为每个接口添加一个新的vtable指针,因此实现的接口数量可能会影响对象大小,而其他实现则不会。同样,实际上如何在内存中组合这些东西是由实现决定的,因此您无法确定这是否会产生内存成本。
据我所知,目前不存在JVM的实现,其中方法的长度会影响对象的大小。通常情况下,每个方法仅存储一份内存,然后该代码跨所有特定对象的实例共享。较长的方法可能需要更多的总内存,但不应对类的实例的每个对象的内存产生影响。也就是说,JVM规范并没有承诺这必须是正确的,但我无法想到任何合理的实现方式,它会为了方法代码而为每个对象分配额外的空间。 除了字段和方法之外,许多其他因素可能会导致对象的大小增加。以下是一些例子: 根据JVM使用的垃圾收集器(或收集器)类型,每个对象可能会有额外的存储空间来保存信息,例如对象是否处于活动、死亡、可达等状态。这可以增加存储空间,但您无法控制。在某些情况下,JVM可能通过尝试将对象存储在堆栈而不是堆上来优化对象大小。在这种情况下,某些类型的对象甚至可能不存在开销。 如果使用同步,对象可能会分配额外的空间,以便可以对其进行同步。JVM的某些实现直到必要时才为对象创建监视器,因此如果您不使用同步,则可能会拥有更小的对象,但无法保证这种情况。 此外,为了支持像instanceof和类型转换之类的操作符,每个对象可能都有一些空间用于保留类型信息。通常,这与对象的vtable捆绑在一起,但不能保证这一点。
如果你使用断言,一些JVM实现将在你的类中创建一个字段,其中包含断言是否已启用。然后在运行时使用它来禁用或启用断言。再次强调,这是与实现相关的,但记住这一点很好。
如果你的类是一个非静态内部类,它可能需要持有对包含它的类的引用,以便可以访问它的字段。但是,如果从未使用它,则JVM可能会优化掉它。
如果您使用匿名内部类,则该类可能需要有额外的空间来保留在其封闭范围内可见的final变量,以便可以在类内部引用它们。这是与实现相关的,无论是将此信息复制到类字段中还是仅在堆栈上本地存储,都可能增加对象大小。
某些实现Object.hashCode()或System.identityHashCode(Object)可能需要在每个包含该哈希码值的对象中存储额外的信息,如果无法以其他方式计算它(例如,如果对象可以在内存中重定位)。这可能会增加每个对象的大小。

这取决于具体的实现方式,这些信息是复制到类字段中还是仅在堆栈上本地存储,但它可能会增加对象大小。除非整个对象可以被优化掉并且不被分配,否则无法将其存储在堆栈上。 - bestsss
@templatetypedef 只是想澄清一下,Java是否使用V表进行动态分派? - peter
@peter 这取决于实现。据我所知,Java 实现通常使用一个 vtable 用于普通继承,但也有通过接口进行动态分派的备用机制(字节码针对这些情况有不同的指令,这让我相信它们可能是以不同的方式完成的)。然而,这完全取决于实现者,他们还可以采用诸如多态内联缓存等技术来补充或替代 vtable。 - templatetypedef

7
为了补充 @templatetypedef 所给出的(尽管有些含糊)数据,这些数字是针对典型的最近32位JVM而言的,但它们是实现特定的:
- 每个对象的头开销通常是2个字,用于普通对象,而数组则需要3个字。头部包括与GC相关的标志以及指向对象实际类的某种指针。对于数组,需要额外的一个字来保存数组大小。 - 如果你(直接或间接地)调用了System.identityHashCode()并且它经历过一次GC周期,则需要添加一个额外的字来存储hashcode值。(现代JVM使用巧妙的技巧来避免为所有对象保留一个hashcode头字段...) - 存储分配粒度可能是字的多个;例如2. - 对象的字段通常是字对齐的;即它们不会紧缩在一起。 - 基本类型数组的元素是紧缩的,但是布尔值通常以紧缩形式的字节表示。 - 引用作为字段和数组元素都占据4个字节。
64位JVM由于某些JVM中的指针压缩(OOPS)而变得更加复杂。此外,我不确定字段是32位还是64位对齐。
请注意:以上内容基于我从各种"有见识的人"在各个地方所听到/阅读到的信息。除了Oracle / Sun之外,没有权威来源提供此类信息,而且(据我所知)他们也没有发布任何内容。

此外,我不确定字段是否为32位或64位对齐。如果是32位,则可能会与int类型产生冲突。至于对齐,有更多信息,但为了使用SSE向量内部函数(例如:http://hg.openjdk.java.net/jdk7/hotspot-comp/hotspot/rev/fbde8ec322d0),某些数组可能需要进一步对齐到128位边界。还有另一个关于SSE的RFE,不知道它是否已经被纳入java7中。 - bestsss

4

GPL许可证而不是LGPL许可证,使得专有应用程序要么无法使用,要么在选择性编译时极为不便。 - Jason S
3
我认为这对于学习目的来说是可以的,我相信这也是问题的意图所在。 - johnnieb
@Jason S - 这就是开发非GPL许可的应用程序的缺点之一。 - Stephen C

0
据我所知,在HBase源代码中,有一些关于对象大小的计算,基于一些常见的规则,如不同字段占用空间的方式。并且在32位或64位操作系统中会有所不同。至少以上所有的人都知道。但我没有详细研究他们为什么这样做。但他们确实在源代码中这样做了。
此外,Java.lang.intrument.Intrumentation类也可以通过getObjectSize()来完成。我猜开源项目也是基于它的。 在这个链接中,有关于如何使用它的详细信息。 在Java中,确定对象大小的最佳方法是什么? 作为评论。实际上,我也很感兴趣,如果你在源代码中这样做,最有意义的用例是什么?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接