【问题标题】:Understanding the difference between a static and a non-static method representation on the heap了解堆上的静态和非静态方法表示之间的区别
【发布时间】:2025-12-08 07:15:02
【问题描述】:

我已经阅读了一些关于这个主题的帖子:

但我对他们所说明的概念感到困惑:

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

因此,无论static 与否,方法都只会在类的堆上存储一份副本。既然类中只有一个副本,我解释为所有方法都属于该类,那为什么Java只能使用实例化的实例调用非静态方法呢?

为什么我们会有非静态方法属于实例而不属于类的概念?

【问题讨论】:

    标签: java static heap-memory permgen


    【解决方案1】:

    故事的另一面需要提及。上课时,说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 中的原因)。编译器使用每个类的静态虚拟表来调用正确的方法。这就是允许多态性的原因。

    【讨论】:

    • 感谢上帝,真的有人在阅读这个问题。 ty 用于解释。
    • @Avi 你能解释一下堆栈和堆内存何时分配以及谁分配的?内存大小是怎么确定的?
    • 每当您调用new 来创建对象实例时,都会分配堆内存。堆栈内存包含您声明的所有原语(int、char、boolean 等)。它还包含对“堆”对象的引用(您将new 结果分配给的变量)。内存大小由您创建的对象定义。启动 java 应用程序时,可以使用 JVM 参数定义总堆内存(-Xms 和 -Xmx 是最著名的)。