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

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

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

我想它是O(1),因为尽管您需要成为Java(vtable,类链)的一些隐藏的蓬松内容的大小会随着继承层次结构的增长而增长,但它们是按类而不是每个实例增长的,并且JVM实现可以将指向这些实体的恒定大小的指针存储在附加到每个的恒定大小标头中。ObjectObject

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


答案 1

当有疑问时,看看源代码(好吧,一个源代码;每个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。这些类本身确实占用了内存中的空间(但每个类只有一次)。深度继承链的运行时效率是另一回事。


答案 2

JVM 规范声明

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

因此,规范并不关心您如何做到这一点。但是...

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

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

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

另外,关于方法条目

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