Java继承的大小成本是多少?

时间:2014-05-30 05:58:18

标签: java oop inheritance jvm internal-representation

在互联网上有各种各样的文章试图在特定的JVM实现中凭经验估计java.lang.Object的开销。例如,我在一些JVM中看到了裸Object estimated at 8 bytes的大小开销。

我想知道的是extends关系的典型JVM实现是否会在类层次结构的每个级别引入增量大小开销。换句话说,假设您有一个具有N级子类的类层次结构。是类实例O(1)还是O(N)?

的内存中表示的开销

我想它是O(1)因为虽然你需要成为Java Object(vtable,类链)的一些隐藏的蓬松东西的大小会随着继承层次结构的增长而增长,但它们会增长每个类,而不是每个实例,JVM实现可以在连接到每个Object的常量大小的头中存储这些实体的常量大小指针。

因此从理论上讲,直接附加到任何Java对象的内存中表示的开销对于继承深度N应该是O(1)。有人知道它在实践中是否正确?

5 个答案:

答案 0 :(得分:23)

The JVM specification states

  

Java虚拟机不强制要求任何特定的内部   对象的结构。

因此规范并不关心你是如何做到的。 ...

  

在Oracle的一些Java虚拟机实现中,a   对类实例的引用是指向自身句柄的指针   一对指针:一对包含方法的表   object和指向Class对象的指针,表示类型   对象,另一个是从堆分配的内存   对象数据。

因此,在典型的Oracle实现中,方法是O(1)。此方法表是每个类的Method Area

  

Java虚拟机有一个在所有人之间共享的方法区域   Java虚拟机线程。方法区域类似于   存储区域用于传统语言或类似的编译代码   到操作系统进程中的“文本”段。它存储   每类结构,如运行时常量池,字段和   方法数据,以及方法和构造函数的代码,包括   在类和实例初始化和中使用的特殊方法(第2.9节)   接口初始化。

此外,关于method entries

  

method_info结构表示由此声明的所有方法   类或接口类型,包括实例方法,类方法,   实例初始化方法(第2.9节),以及任何类或接口   初始化方法(§2.9)。 方法表不包含项目   表示从超类继承的方法   超接口。

答案 1 :(得分:22)

如果有疑问,请查看source(以及 a 来源;每个JVM都可以自由选择如何操作,因为标准不强制要求任何内部表示)。所以我看一下,在JDK 7-u60的热点JVM的实现中找到了following comment

// A Klass is the part of the klassOop that provides:
//  1: language level class object (method dictionary etc.)
//  2: provide vm dispatch behavior for the object
// Both functions are combined into one C++ class. The toplevel class "Klass"
// implements purpose 1 whereas all subclasses provide extra virtual functions
// for purpose 2.

// One reason for the oop/klass dichotomy in the implementation is
// that we don't want a C++ vtbl pointer in every object.  Thus,
// normal oops don't have any virtual functions.  Instead, they
// forward all "virtual" functions to their klass, which does have
// a vtbl and does the C++ dispatch depending on the object's

我读它的方式,这意味着,对于这个(非常流行的)实现,对象实例只存储指向其类的指针。 具有更长或更短的继承链的类的每个实例的成本实际上是0 。这些类本身确实占用了内存空间(但每个类只有一次)。深度继承链的运行时效率是另一回事。

答案 2 :(得分:4)

一个实例通常需要以下数据,尽管它完全取决于实现:

  • 该类及其父类的实例字段,我假设您意味着包含在术语"开销"
  • 一些锁定对象的方法
  • 如果垃圾收集器重定位对象,则有一些方法可以记录对象的原始哈希值(对于Object.hashCode
  • 访问类型信息的一些方法

正如您在问题中所猜测的那样,在正常情况下,#34; Java实现类型信息是按类存储的,而不是每个实例存储的。 " type"的定义的一部分;是同一类的两个实例必然具有相同的类型信息,没有明显的理由不共享它。所以你会期望每个实例的开销是恒定的,而不是依赖于类层次结构。

也就是说,向类中添加额外的空类或接口不应该增加其实例的大小。我不认为语言或JVM规范实际上保证了这一点,所以不要对什么是非正常的"做出太多的假设。允许Java实现。

顺便说一下,我列表中的第二个和第三个东西可以通过狡猾的技巧组合在一起,这样它们就可以组成一个指针。你链接的文章是指引用4个字节的引用,因此它为对象提供的8个字节是一个指向类型信息的指针,一个字段包含 哈希码指向监视器的指针,可能是这些指针字段中的一个或两个的最低2位中的一些标志。 Object将(您预期)在64位Java上更大。

答案 3 :(得分:2)

Double和Integer,它扩展了Number,它扩展了Object,没有O(n)行为,也就是说,Integer的大小不是Object的3倍,所以我认为答案是O(1)。例如见this old SO question

答案 4 :(得分:1)

  

理论上,直接附加到任何Java对象的内存中表示的开销对于继承深度N应该是O(1)。有人知道它在实践中是否正确?

除非每个级别都有零实例成员,否则它不能是O(1)。每个实例成员每个实例都需要空间。