还有一个需要提到的故事。当你有一个类,比如说Bar
:
public class Bar {
public static void doSomething() { ... }
public void doSomethingElse() { ... }
}
在堆上,doSomethingElse
的签名不是 doSomethingElse()
而是 doSomethingElse(Bar this)
。与此不同的是,doSomething
没有参数(因此您无法从静态方法调用 this
- 没有可调用的 this
)。
当你有这样一个调用:
Bar bar = new Bar();
bar.doSomethingElse();
这只是一种语法糖,其目的在于:
doSomethingElse(bar); // I neglected here for simplification but the name of the method in the compiled code also includes the name of the class.
当您定义一个扩展类
Foo
时:
public class Foo extends Bar {
@Override
public void doSomethingElse() { ... }
}
另一种方法是创建
doSomethingElse(Foo this)
。接下来有一个虚拟表格(如果您不熟悉这个术语,请了解一下) - 每个类都持有一个虚拟表格,将方法签名映射到具体代码。当您在运行时调用方法时,会在类(而不是实例)的虚拟表格中搜索正确的实现,根据实例的动态类型。
因此,完整的示例是(当然这只是一个简化版):
Java语法(语法糖):
Bar b = new Foo();
b.doSomethingElse();
实际发生的情况(简化版):
// Step 1: Get the correct overriden method for Foo class from the virtual table
// Step 2: Invoke the method with the instance "b" as the first parameter (the "this" parameter)
Foo.getMethodFromVirtualTable("doSomethingElse").invoke(b);
当然,这只是一个简化的说法,但大致上确实是这样发生的。
事实上,如果你仔细思考,所有的方法都是静态存储在内存中的(这就是为什么它们驻留在PermGen
中)。编译器使用每个类的静态虚拟表来调用正确的方法。这就是多态的实现方式。
new
创建对象实例时,都会分配堆内存。栈内存包含您声明的所有基本类型(int、char、boolean等),还包含对“堆”对象的引用(将new
结果分配给的变量)。内存大小由您创建的对象定义。在启动Java应用程序时,可以使用JVM参数(-Xms和-Xmx是最著名的参数)来定义总堆内存。 - Avi