繁体   English   中英

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

[英]What's the size cost of Java inheritance?

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

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

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

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

JVM规范说明

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

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

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

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

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

另外,关于方法条目

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

如有疑问,请查看 (当然, ;每个JVM可以自由选择如何做到这一点,因为标准不强制要求任何内部表示)。 所以我看一下,在JDK 7-u60的热点JVM的实现中发现了以下注释

// 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 这些类本身确实占用了内存空间(但每个类只有一次)。 深度继承链的运行时效率是另一回事。

一个实例通常需要以下数据,尽管这取决于实现的具体操作:

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

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

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

顺便说一句,我的列表中的第二和第三个东西可以通过狡猾的技巧组合在一起,这样它们两个在一起就是一个指针。 链接到所述制品是指服用4个字节的引用,因此它可以与出现的某个对象的8个字节是一个指针,指向键入包含任一的哈希码一个指向一个监视器,在最低的可能一些标志信息,一个场这些指针字段中的一个或两个的2位。 Object (你期望)在64位Java上更大。

扩展Number的扩展Object的Double和Integer没有O(n)行为,也就是说,Integer的大小不是Object的3倍,所以我认为答案是O(1)。 例如,看到这个旧的SO问题

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM