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

Jux*_*ion 41 java memory-management

是什么促成了内存中单个对象的大小?

我知道原语和引用会,但还有什么吗?方法的数量和长度是否重要?

tem*_*def 65

这完全取决于实现,但是有一些因素会影响Java中的对象大小.

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

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

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

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

除了字段和方法之外,许多其他因素可能会影响对象的大小.这里有几个:

根据JVM使用的垃圾收集器(或收集器)的类型,每个对象可能有额外的存储空间来保存有关对象是活动的,死的,可访问的等信息.这可以增加存储空间,但它不在你的控制.在某些情况下,JVM可能通过尝试将对象存储在堆栈而不是堆上来优化对象大小.在这种情况下,某些类型的对象甚至可能不存在开销.

如果使用同步,则对象可能会为其分配额外的空间,以便可以同步它.JVM的某些实现在有必要之前不会为对象创建监视器,因此如果不使用同步,最终可能会有较小的对象,但您不能保证会出现这种情况.

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

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

如果您的类是非静态内部类,则可能需要保存对包含它的类的引用,以便它可以访问其字段.但是,如果您最终没有使用它,JVM可能会优化它.

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

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


Ste*_*n C 7

向@ templatetypedef的优秀答案添加一些(公认的模糊)数据.这些数字适用于典型的最新32位JVM,但它们是特定于实现的:

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

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

  • 存储分配粒度可以是多个单词; 例如2.

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

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

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

由于某些JVM中的指针压缩(OOPS),64位JVM的情况要复杂一些.另外,我不确定字段32或64位是否对齐.


(注意:以上是基于我在各个地方听到/读过的各种"知识渊博的人".除了Oracle/Sun之外,这种信息没有明确的来源,而且(AFAIK)他们还没有发表这些信息.任何东西.)