Java运行时内存模型--

Java运行时内存模型--,java,runtime,heap-memory,Java,Runtime,Heap Memory,我正在寻找以下方面的验证/更正 假设以下继承层次结构- 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); } // overr

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

假设以下继承层次结构-

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和类B都加载到内存中 通过静态加载程序。 这两个类及其所有成员方法的定义都在内存中

当用上述语句实例化B时,堆中的内存空间 是为该对象b分配的。 方法m1、m2和m99都在b的空间中有它们的定义

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

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

如果该方法是从超类继承的,例如上面的m2,则该方法的定义 方法在类别中,上文第A段中提到的定义本身就是对类别A中m2定义的引用

在运行时,当执行b.m2时,JRE直接转到类A以查找要执行的低级操作的模板

这些对方法定义的引用在编译时被检查并放入字节码中。在上述情况下的字节码中,类B直接引用了从a继承的方法m2的类a的方法m2

这些都准确吗?如果没有,在哪里/为什么没有

方法m1、m2和m99都在b的空间中有它们的定义

不对。分配给b的空间b的实例引用类本身,即分配给类b的空间,在该空间中存储方法定义

为对象实例分配的空间由对象头和实例数据(即字段值)组成。如需有关对象标题的更多信息,请参阅

在上述情况下的字节码中,类B直接引用了从a继承的方法m2的类a的方法m2


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

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

更新

发件人:

那么,如何知道在运行b.m2时执行什么?我不认为JRE去了B的超类,在那里看到了m2,如果没有这样的方法,那么去了超类。。。运行时效率太低。必须是对m2的直接引用。m2是B的成员-即使是继承的

如回答中所述,m2不是B的成员。如果您运行Java反汇编程序,即在命令行上运行javap B.class,您将看到:

B类扩展了A类{ int x; B 空位m1; 无效m99; } 如您所见,编译器为您添加了默认构造函数,但没有添加任何m2方法

现在创建这个类:

C类{ 公共静态无效字符串[]args{ B=新的B; b、 m2; } } 然后用-c开关将其拆开,即javap-c.class:

C类{ C 代码: 0:aload_0 1:invokespecial 8//方法java/lang/Object.:V 4:返回 公共静态void mainjava.lang.String[]; 代码: 0:新16//B类 3:dup 4:invokespecial 18//方法B:V 7:astore_1 8:aload_1 9:invokevirtual 19//方法B.m2:V 12:返回 } 如您所见,编译器生成一条调用B.m2的指令,尽管我们已经看到B.class不知道m2

这意味着您假设的正是发生的情况,即JVM需要在运行时通过向上遍历超类链将方法解析为类A

如果将m2从类A中删除并重新编译,而不重新编译类C,那么在运行代码时,您将得到NoSuchMethodError:“void B.m2”

方法m1、m2和m99都在b的空间中有它们的定义

不对。分配给b的空间b的实例引用类本身,即分配给类b的空间,在该空间中存储方法定义

为对象实例分配的空间由对象头和实例数据(即字段值)组成。如需有关对象标题的更多信息,请参阅

在上述情况下的字节码中,B类有,a 对于方法m2,直接引用类A的方法m2,它从A继承


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

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

更新

发件人:

那么,如何知道在运行b.m2时执行什么?我不认为JRE去了B的超类,在那里看到了m2,如果没有这样的方法,那么去了超类。。。运行时效率太低。必须是对m2的直接引用。m2是B的成员-即使是继承的

如回答中所述,m2不是B的成员。如果您运行Java反汇编程序,即在命令行上运行javap B.class,您将看到:

B类扩展了A类{ int x; B 空位m1; 无效m99; } 如您所见,编译器为您添加了默认构造函数,但没有添加任何m2方法

现在创建这个类:

C类{ 公共静态无效字符串[]args{ B=新的B; b、 m2; } } 然后用-c开关将其拆开,即javap-c.class:

C类{ C 代码: 0:aload_0 1:invokespecial 8//方法java/lang/Object.:V 4:返回 公共静态void mainjava.lang.String[]; 代码: 0:新16//B类 3:dup 4:invokespecial 18//方法B:V 7:astore_1 8:aload_1 9:invokevirtual 19//方法B.m2:V 12:返回 } 如您所见,编译器生成一条调用B.m2的指令,尽管我们已经看到B.class不知道m2

这意味着您假设的正是发生的情况,即JVM需要在运行时通过向上遍历超类链将方法解析为类A


如果将m2从类A中删除并重新编译,而不重新编译类C,则在运行代码时将不会出现“void B.m2”这样的方法错误。

如果使用类似的反汇编程序,则在.class文件中都可以看到。类B的字节码不包含方法m2。进一步阅读有关Java句柄继承的内容。

如果您使用类似的反汇编程序,那么它在.class文件中都是可见的。类B的字节码不包含方法m2。进一步阅读有关Java处理继承的内容。

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

构建应用程序时,类A和类B都加载到内存中 通过静态加载程序。 这两个类及其所有成员方法的定义都在内存中

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

当用上述语句实例化B时,堆中的内存空间 是为该对象b分配的。 方法m1、m2和m99都在b的空间中有它们的定义

因此,方法定义是JVM类表示的一部分。该对象仅包含指向该类的引用指针

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

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

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

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

如果该方法是从超类继承的,例如上面的m2,则该方法的定义 方法在类别中,上文第A段中提到的定义本身就是对类别A中m2定义的引用

在运行时,当b.m2 执行后,JRE直接转到类A,以查找要执行的低级操作的模板

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

这些对方法定义的引用在编译时被检查并放入字节码中。在上述情况下的字节码中,类B直接引用了从a继承的方法m2的类a的方法m2

这不是Java的工作方式。在Java中,编译器将检查是否存在被调用的方法,该方法在B从A继承方法并可访问时成功,然后,它将记录在源代码中对B上调用的m2的调用

B继承了该方法,这是B的一个实现细节,允许更改。B的未来版本可能会覆盖该方法。如果发生这种情况,A.m2甚至可能被移除。或者在a和B之间引入C类C扩展a和B扩展C,这都是向后兼容的更改

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

另一种策略是使用上面提到的“vtable”。这样的表在初始化时为每个类创建,首先是所有超类方法的副本、替换的重写方法的条目,最后是新声明的方法

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

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

JVM使用A.m2从未覆盖的知识,当加载一个覆盖该方法的新类时,它必须删除这样的优化

它分析代码路径,以确定对于B B=新B;b、 m2;目标是固定的,因为新的B始终是类型B,而不是超类和子类

当预测目标时,它可以内联。然后,优化后的代码只需执行System.out.printnew Date;当B实例没有其他用途时,甚至分配也可能被消除


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

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

构建应用程序时,类A和类B都加载到内存中 通过静态加载程序。 这两个类及其所有成员方法的定义都在内存中

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

当用上述语句实例化B时,堆中的内存空间 是为该对象b分配的。 方法m1、m2和m99都在b的空间中有它们的定义

因此,方法定义是JVM类表示的一部分。该对象仅包含指向该类的引用指针

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

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

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

你应该忘记“模板”这个词。方法定义包含一系列可执行指令,JVM将执行这些指令。比如冰毒 ods中,指向对象数据的指针成为隐式的第一个参数。任何模板都不会填充任何内容

如果该方法是从超类继承的,例如上面的m2,则该方法的定义 方法在类别中,上文第A段中提到的定义本身就是对类别A中m2定义的引用

在运行时,当执行b.m2时,JRE直接转到类A以查找要执行的低级操作的模板

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

这些对方法定义的引用在编译时被检查并放入字节码中。在上述情况下的字节码中,类B直接引用了从a继承的方法m2的类a的方法m2

这不是Java的工作方式。在Java中,编译器将检查是否存在被调用的方法,该方法在B从A继承方法并可访问时成功,然后,它将记录在源代码中对B上调用的m2的调用

B继承了该方法,这是B的一个实现细节,允许更改。B的未来版本可能会覆盖该方法。如果发生这种情况,A.m2甚至可能被移除。或者在a和B之间引入C类C扩展a和B扩展C,这都是向后兼容的更改

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

另一种策略是使用上面提到的“vtable”。这样的表在初始化时为每个类创建,首先是所有超类方法的副本、替换的重写方法的条目,最后是新声明的方法

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

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

JVM使用A.m2从未覆盖的知识,当加载一个覆盖该方法的新类时,它必须删除这样的优化

它分析代码路径,以确定对于B B=新B;b、 m2;目标是固定的,因为新的B始终是类型B,而不是超类和子类

当预测目标时,它可以内联。然后,优化后的代码只需执行System.out.printnew Date;当B实例没有其他用途时,甚至分配也可能被消除


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

类B的字节码对方法m2一无所知-那么如何知道在运行B.m2时执行什么?我不认为jre去了B的超类,在那里看到了m2,如果没有这样的方法,那么去了超类。运行时效率太低。必须是对m2的直接引用。m2是B的一个成员-即使是继承的。B类的字节码对方法m2一无所知-那么当B.m2运行时,如何知道执行什么呢?我不认为jre去了B的超类,在那里看到了m2,如果没有这样的方法,那么去了超类。运行时效率太低。必须是对m2的直接引用。m2是B的成员,即使是继承的。注意:这与Java内存模型无关。这是关于在多线程应用程序中何时以及是否可以看到对变量所做的更改。注意:这与Java内存模型无关。这是关于在多线程应用程序中何时以及是否可以看到对变量所做的更改。标签被移除。