如果将非static
函数复制到具有该方法的每个对象的堆中,那么为什么默认情况下不是Java static
中的所有方法都没有?为什么要这样浪费所有的堆内存?
图解说明将有助于我理解这一点。
通常,Java方法是通过每个对象一次将方法复制到堆上而实现的。取而代之的是,通常使用称为virtual function table(或“ vtable”)的东西来实现方法。这个想法是每个方法只有一个副本,无论是静态方法还是非静态方法,并且这些方法的指针都放置在一个表中。然后,堆中的每个对象都会为其对象类型存储指向vtable的指针。这意味着任何堆对象的大小都不取决于该对象拥有的方法的数量。实际上,具有100个方法的对象与具有1个方法的对象具有相同的大小(假设它们具有相同的字段)。每个对象都只存储一个指向其对象类型的vtable的指针,该对象只有一个副本。此优化最初在C ++中用于支持快速虚拟函数,此后已在许多其他面向对象的语言中使用。它允许对象较小,但支持动态分配。
换句话说,方法不必默认为static
,因为它们不会增加堆中对象的大小。对于具有更多功能的对象,创建对象不会花费更长的时间,也不会占用更多的堆空间。
这是一些对象的布局的可能示意图(为ASCII艺术表示歉意!)。假设我们有两个类,A和B。然后在内存中,这些类型的对象可能看起来像这样:
A vtable for A
+-------------+ +---------------+
| vtable ptr | --+-> | method one |
+-------------+ | +---------------+
| | | | method two |
| fields of A | | +---------------+
| | | | ... |
+-------------+ | +----------------
| | method N |
A | +---------------+
+-------------+ |
| vtable ptr |---+
+-------------+
| |
| fields of A |
| |
+-------------+
B vtable for B
+-------------+ +------------+
| vtable ptr | --> | method one |
+-------------+ +------------+
| | | method two |
| fields of B | +------------+
| | | ... |
+-------------+ +------------+
| method M |
+------------+
注意类型A的两个对象如何共享相同的vtable,以及类型A和B的对象如何仅为其vtable指针使用相同的空间,即使它们具有不同数量的方法。