3

在网上搜索后,我还没有找到关于实例变量在 Java 内存模型中的确切位置的良好而全面的答案。例如,我们有这段代码(带有变量的阴影声明):

class A {
    int var = 1;
    void m() {
        System.out.println("\'m()\' is called from class A");
    }
}

class B extends A {
    int var = 5;
    void m() {
        System.out.println("\'m()\' is called from class B");
    }
}

public class Class1 {
    public static void main(String args[]) {
        A aref = new B();
        aref.m();
        String s = (aref.var)==1?"A":"B";
        System.out.println("\'var\' is called from class " + s);
    }
}

这段代码的输出是:

'm()' is called from class B
'var' is called from class A

现在的问题不是继承在 Java 中是如何工作的,而是这个实例变量驻留在 Java 内存模型中的什么位置?请论证你的答案。

谢谢

4

2 回答 2

4

一个对象保存在堆内,但作为一个内存块,等于所有变量组合的大小+一些额外的字节来存储虚拟方法表(VMT)和对象的原型位置(可能更多,取决于 JVM 实现) .

因此,您的示例对象在(32 位)内存中看起来像这样(指针值仅用于演示):

[0000] 0154  // pointer to prototype
[0004] 3625  // pointer to virtual method table
[0008] 0001  // int var

现在在上面的示例中,没有访问任何成员,因此 JVM 所做的只是定位该原型的 VMT,跳转到那里写下的函数地址并执行它。

如果您的var = 1代码实际上通过了优化器,则生成的汇编代码将不知道这个“var”的东西,而是使用直接内存访问。像这样的东西:

set [4924 + 8], 1 

其中 4924 是实例的内存位置, + 8 是变量的偏移量var。请记住,这是(使人类可读的)程序集而不是字节码,所以基本上 JIT 完成后剩下的就是它。

由于您的两个对象的大小相同,甚至可以将 A“向上转换”到 B,它不起作用的唯一原因是因为 Java 禁止此类不安全的操作。在其他不太安全的语言(如 C++)中,您可以轻松地做到这一点,并且可能会侥幸成功。

于 2013-11-14T16:47:30.777 回答
1

继承时变量与方法不同。您的方法 m() 在扩展 A 时在 B 类中被覆盖。但是,B 不能覆盖其父类的局部变量,因为它对它们没有任何管辖权。

于 2013-11-14T16:23:22.977 回答