繁体   English   中英

Java运行时内存模型——

[英]Java runtime memory model --

我正在寻找以下方面的验证/更正。

假设以下继承层次结构 -

class A {
    void m1() { System.out.print(System.currentTimeMillis()); }
    void m2() { System.out.print(new Date()); }  // current date

}

class B extends A {
    int x;
    void m1() { System.out.print(x); }  // overriding
    void m99() { System.out.print(++x); }  
}

还假设类B在应用程序中的某个点被实例化,即执行以下语句 -

B b = new B();

(A 段)在构建应用程序时,类AB都由静态加载器加载到内存中。 这两个类以及它们所有成员方法的定义都在内存中。

B用上面的语句实例化时,堆中的内存空间被分配给那个对象b 方法m1()m2()m99()b这个空间中都有它们的定义。

这些方法定义只是对存在于类定义中的方法“模板”的引用。 在类中,这些方法是操作序列——如果方法在任何参数和/或全局变量上执行,则参数化操作。

当其中一个方法,比如说b.m99()在运行时被调用时,JRE 转到B类定义以获取该“模板”(操作序列),查找b字段的当前值,并填写具有这些字段的当前值的“模板”还将这些当前值推送到堆栈空间,并通过执行在类定义中找到的这些操作来运行方法。

如果该方法是从超类继承的,例如。 上面的m2() ,类中该方法的定义(上面 A 段中提到的定义)本身就是对类 A 中m2()定义的引用。

在运行时,当b.m2()被执行时,JRE 会直接进入A类以找到要执行的低级操作的“模板”。

这些对方法定义的引用在编译时被检查并放入字节码中。 例如。 在字节码为上述的情况下,类B有,直接引用方法m2()类的A用于方法m2()它继承A

这一切都准确吗? 如果没有,在哪里/为什么不呢?

一般来说,Java 的执行环境可以通过多种方式实现,不可能说“Java”一般是做什么的。

构建应用程序时,类AB都由静态加载器加载到内存中。 这两个类以及它们所有成员方法的定义都在内存中。

部署的标准方式是将 Java 源代码编译为字节码。 当应用程序被执行时,类将被加载。 没有“静态加载器”这样的东西。 有不同的类加载器。 当类文件在类路径上传递时,它们将由应用程序类加载器加载

B用上面的语句实例化时,堆中的内存空间被分配给那个对象b 方法m1()m2()m99()b这个空间中都有它们的定义。

正如Andreas 所说,方法定义是 JVM 类表示的一部分。 该对象仅包含对类的引用(指针)。

这些方法定义只是对存在于类定义中的方法“模板”的引用。 在类中,这些方法是操作序列——如果方法在任何参数和/或全局变量上执行,则参数化操作。

术语“定义”和“模板”以及您使用它们的方式正在造成不必要的混淆。 指令序列是方法定义的一部分。 对象引用这些定义,或者通过已经提到的对类的引用间接引用,或者直接通过方法指针表(称为“vtable”),这是一种广泛的优化。

当其中一个方法,比如说b.m99()在运行时被调用时,JRE 转到B类定义以获取该“模板”(操作序列),查找b字段的当前值,并填写具有这些字段的当前值的“模板”还将这些当前值推送到堆栈空间,并通过执行在类定义中找到的这些操作来运行方法。

你应该忘记“模板”这个词。 方法定义包含一系列可执行指令,JVM 将执行这些指令。 对于实例方法,指向对象数据的指针成为隐式的第一个参数。 任何模板都不会填充任何东西。

如果该方法是从超类继承的,例如。 上面的m2() ,类中该方法的定义(上面 A 段中提到的定义)本身就是对类 A 中m2()定义的引用。

在运行时,当b.m2()被执行时,JRE 会直接进入A类以找到要执行的低级操作的“模板”。

这是一个实现细节,但请屏住呼吸……

这些对方法定义的引用在编译时被检查并放入字节码中。 例如。 在字节码为上述的情况下,类B有,直接引用方法m2()类的A用于方法m2()它继承A

这不是 Java 的工作原理。 在 Java 中,编译器将检查被调用的方法是否存在,当BA继承方法及其可访问性时,编译器会成功,然后,它将记录在源代码中编写的调用,用于在B调用的m2()

这一事实B继承的方法中,是的实现细节B并使其变化。 的未来版本B可覆盖的方法。 如果发生这种情况, A.m2()甚至可能会被删除。 或者可能会在AB之间引入类CC extends AB extends C ),它们都是向后兼容的更改。

但是回到上一节,在运行时,实现可能会利用有关实际继承的知识。 每次调用一个方法时,JVM 都可以搜索超类型层次结构,这可能是有效的,但效率不高。

一个不同的策略是拥有上面提到的“vtable”。 每个类在初始化时都会创建一个这样的表,从所有超类方法的副本开始,被覆盖的方法的条目被替换,最后是新声明的方法。

因此,当第一次执行调用指令时,它通过确定 vtable 中的关联索引来链接。 然后,每次调用只需要从对象的实际类的 vtable 中获取方法指针,而无需遍历类层次结构。

这仍然是唯一的方式,解释或优化程度较低的执行工作。 当 JVM 决定进一步优化调用代码时,它可能会预测方法调用的实际目标。 您的示例中有两种方法

  • JVM 使用A.m2()从未覆盖的知识A.m2()当加载新类时它必须删除这样的优化,它确实覆盖了方法)

  • 它分析代码路径,以确定对于B b = new B(); b.m2(); B b = new B(); b.m2(); 目标是固定的,因为new B()的结果总是B类型,不是超类也不是子类。

当目标被预测时,它可以被内联。 然后,优化后的代码简单地执行System.out.print(new Date()); B实例没有其他用途时,甚至分配可能会被消除。

因此,JVM 在运行时所做的可能与源代码中编写的完全不同。 只有可感知的结果(打印日期)是相同的。

方法m1()m2()m99()b这个空间中都有它们的定义。

不正确。 b分配的空间( B的实例)引用类本身,即为类B分配的空间,方法定义存储在其中。

为对象实例分配的空间由对象头和实例的数据(即字段的值)组成。 有关对象头的更多信息,请参见例如java 对象头中的内容。

例如。 在字节码为上述的情况下,类B有,直接引用方法m2()类的A用于方法m2()它继承A

不正确。 B类的字节码对方法m2()一无所知。

请记住,类A可以与类B分开编译,因此您可以删除方法m2而无需重新编译类B


更新

来自评论

那么如何知道在运行b.m2()时要执行什么? 我不认为 JRE 进入B的超类,看起来在那里看到一个m2() ,如果没有这样的方法然后进入超超类,......运行时效率太低。 必须是对m2()的直接引用。 m2()B的成员——即使是继承的。

正如答案中已经说明的那样, m2()不是B的成员。 如果您运行 Java 反汇编程序,即在命令行上运行javap B.class ,您将看到:

class B extends A {
  int x;
  B();
  void m1();
  void m99();
}

如您所见,编译器为您添加了默认构造函数,但没有添加任何m2()方法。

现在创建这个类:

class C {
    public static void main(String[] args) {
        B b = new B();
        b.m2();
    }
}

然后用-c开关反汇编它,即javap -c C.class

class C {
  C();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #16                 // class B
       3: dup
       4: invokespecial #18                 // Method B."<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #19                 // Method B.m2:()V
      12: return
}

如您所见,编译器生成一条指令来调用B.m2() ,即使我们已经看到B.class不知道m2()

这意味着您所假设的正是发生的事情,即 JVM 需要在运行时通过沿超类链向上解析该方法到A类。

如果从类A删除m2()并重新编译,而不重新编译类C ,则在运行代码时会得到NoSuchMethodError: 'void B.m2()'

如果您使用像javap这样的反汇编程序,它在 .class 文件中都是可见的。 B 类的字节码不包含方法 m2。 在此处进一步阅读有关 Java 处理继承的信息

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM