理解静态和非静态方法在堆上的表示方式的区别

4
我已经阅读了一些关于这个话题的帖子: 但我对它们所阐述的概念感到困惑:

静态方法(实际上所有方法)以及静态变量都存储在PermGen区域的堆中,因为它们是反射数据的一部分(与实例无关的类相关数据)。

所以方法,无论是 `static` 还是非 `static`,只在类的堆上存储一份。既然只有一个副本存在于类中,我将其解释为所有方法都属于类,那么为什么Java只能使用实例化的实例调用非静态方法呢?
为什么我们有“非静态方法属于实例而不是类”的概念呢?
1个回答

6

还有一个需要提到的故事。当你有一个类,比如说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中)。编译器使用每个类的静态虚拟表来调用正确的方法。这就是多态的实现方式。


2
谢天谢地,有人真的在阅读这个问题。虽然解释感谢你。 - guness
@Avi,你能解释一下什么时候会分配堆栈内存以及由谁进行分配吗?内存大小是如何确定的? - user6091735
每当您调用new创建对象实例时,都会分配堆内存。栈内存包含您声明的所有基本类型(int、char、boolean等),还包含对“堆”对象的引用(将new结果分配给的变量)。内存大小由您创建的对象定义。在启动Java应用程序时,可以使用JVM参数(-Xms和-Xmx是最著名的参数)来定义总堆内存。 - Avi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接