是什么决定了 Java 对象的大小?

2022-09-01 01:28:17

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

我知道原语和引用会,但是还有其他东西吗?方法的数量和长度重要吗?


答案 1

这完全依赖于实现,但是有几个因素会影响Java中的对象大小。

首先,Java 对象中字段的数量和类型肯定会影响空间使用,因为您需要至少拥有与保存对象所有字段所需的存储空间一样多的存储空间。但是,由于填充、对齐和指针压缩优化,因此没有直接公式可用于精确计算以这种方式使用的空间量。

至于方法,通常说对象中的方法数量对其大小没有影响。方法通常使用称为虚函数表(或“vtables”)的功能来实现,该功能可以在常量时间内通过基类引用调用方法。这些表通常是通过让 vtable 的单个实例在多个对象之间共享来存储的,然后让每个对象存储一个指向 vtable 的指针。

接口方法使这幅图变得有点复杂,因为有几种不同的实现可能。一个实现为每个接口添加一个新的 vtable 指针,因此实现的接口数可能会影响对象大小,而其他实现则不会影响对象大小。同样,它的实现取决于事物在内存中的实际组合方式,因此您无法确定这是否会有内存成本。

据我所知,目前还没有JVM的实现,其中方法的长度会影响对象的大小。通常,每个方法只有一个副本存储在内存中,然后代码在特定对象的所有实例之间共享。具有较长的方法可能需要更多的内存,但不应影响类实例的每个对象内存。也就是说,JVM规范没有承诺必须如此,但我想不出一个合理的实现会为每个对象花费额外的空间来编写方法代码。

除了字段和方法之外,许多其他因素也会影响对象的大小。以下是一些:

根据 JVM 使用的垃圾回收器(或多个)类型,每个对象可能都有额外的存储空间来保存有关对象是否处于活动状态、已死项、可访问等信息。这可能会增加存储空间,但这是你无法控制的。在某些情况下,JVM 可能会通过尝试将对象存储在堆栈而不是堆上来优化对象大小。在这种情况下,某些类型的对象甚至可能不存在开销。

如果使用同步,则对象可能为其分配了额外的空间,以便可以对其进行同步。JVM 的某些实现在必要之前不会为对象创建监视器,因此,如果不使用同步,最终可能会拥有较小的对象,但不能保证会是这样。

此外,为了支持类似运算符和类型转换,每个对象可能保留一些空间来保存类型信息。通常,这与对象的 vtable 捆绑在一起,但不能保证这是真的。instanceof

如果使用断言,某些 JVM 实现将在类中创建一个字段,其中包含是否启用断言。然后,这将用于在运行时禁用或启用断言。同样,这是特定于实现的,但最好记住。

如果您的类是非静态内部类,则可能需要保留对包含它的类的引用,以便可以访问其字段。但是,如果您从未最终使用它,JVM可能会对此进行优化。

如果使用匿名内部类,则该类可能需要保留额外的空间来保存在其封闭作用域中可见的变量,以便可以在类中引用它们。无论将此信息复制到类字段中还是仅本地存储在堆栈上,它都是特定于实现的,但它会增加对象大小。final

如果无法以任何其他方式计算该哈希代码的值(例如,如果对象可以在内存中重新定位),则或可能需要将额外信息存储在包含该哈希代码值的每个对象中。这可能会增加每个对象的大小。Object.hashCode()System.identityHashCode(Object)


答案 2

为了给@templatetypedef添加一些(公认模糊的)数据。这些数字适用于典型的最近 32 位 JVM,但它们是特定于实现的

  • 每个对象的标头开销通常为常规对象的 2 个单词,数组的标头开销为 3 个单词。标头包括与 GC 相关的标志,以及指向对象实际类的某种指针。对于数组,需要一个额外的单词来保存数组大小。

  • 如果您(直接或间接)调用了某个对象,并且该对象在 GC 周期中幸存下来,请添加一个额外的单词来存储哈希码值。(现代JVM使用一个聪明的技巧来避免为所有对象保留哈希码头字段......System.identityHashCode()

  • 存储分配粒度可以是单词的倍数;例如 2.

  • 对象的字段通常是单词对齐的;即他们没有包装。

  • 基元类型数组的元素被打包,但布尔值通常由打包形式的字节表示。

  • 引用作为字段和数组元素占用 4 个字节。

对于 64 位 JVM 来说,事情有点复杂,因为某些 JVM 中存在指针压缩 (OOPS)。另外,我不确定字段32位还是64位对齐。


(注意:以上是基于我在不同地方从各种“知识渊博的人”那里听到/读到的内容。除了Oracle / Sun之外,没有这种信息的确切来源,并且(AFAIK)他们没有发布任何内容。