简体   繁体   English

Java运行时内存模型——

[英]Java runtime memory model --

I'm looking for verification/correction on the following.我正在寻找以下方面的验证/更正。

Assume the following inheritance hierarchy -假设以下继承层次结构 -

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); }  
}

Also assume that, class B is instantiated at some point in the application, ie the following statement is executed -还假设类B在应用程序中的某个点被实例化,即执行以下语句 -

B b = new B();

(Paragraph-A) When the application is built, both classes A and B are loaded into memory by the static loader. (A 段)在构建应用程序时,类AB都由静态加载器加载到内存中。 Both classes are in memory, along with the definitions of all their member methods.这两个类以及它们所有成员方法的定义都在内存中。

When B is instantiated with the above statement, a memory space in the heap is allocated for that object b .B用上面的语句实例化时,堆中的内存空间被分配给那个对象b The methods m1() , m2() and m99() all have their definitions in this space of b .方法m1()m2()m99()b这个空间中都有它们的定义。

These method definitions are only references to the method "templates" existing in the class definitions.这些方法定义只是对存在于类定义中的方法“模板”的引用。 In the class, these methods are sequences of operations-- parameterized operations if the method is executing on any parameter and/or global variable.在类中,这些方法是操作序列——如果方法在任何参数和/或全局变量上执行,则参数化操作。

When one of the methods, say b.m99() is invoked at runtime, JRE goes to class definition of B to get that "template" (sequence of operations), looks up the current values of the fields of b , fills in that "template" with the current values of these field(s), also pushes these current values to the stackspace and runs the methods by executing these operations it found on the class definition.当其中一个方法,比如说b.m99()在运行时被调用时,JRE 转到B类定义以获取该“模板”(操作序列),查找b字段的当前值,并填写具有这些字段的当前值的“模板”还将这些当前值推送到堆栈空间,并通过执行在类定义中找到的这些操作来运行方法。

If the method is inherited from a superclass, Eg.如果该方法是从超类继承的,例如。 m2() above, the definition of that method in the class (the definition mentioned in Paragraph-A above) is itself a reference to the definition of m2() in class A.上面的m2() ,类中该方法的定义(上面 A 段中提到的定义)本身就是对类 A 中m2()定义的引用。

At runtime, when b.m2() is executed, JRE goes directly to class A to find that "template" for the low-level operations to execute.在运行时,当b.m2()被执行时,JRE 会直接进入A类以找到要执行的低级操作的“模板”。

These references to method definitions are checked at compile time and put into the bytecode.这些对方法定义的引用在编译时被检查并放入字节码中。 Eg.例如。 in the bytecode for the above case, class B has, a direct reference to method m2() of class A for method m2() it's inheriting from A .在字节码为上述的情况下,类B有,直接引用方法m2()类的A用于方法m2()它继承A

Is this all accurate?这一切都准确吗? If not, where/why not?如果没有,在哪里/为什么不呢?

Generally, execution environments for Java can get implemented in various ways and it is impossible to say what “Java” does in general.一般来说,Java 的执行环境可以通过多种方式实现,不可能说“Java”一般是做什么的。

When the application is built, both classes A and B are loaded into memory by the static loader.构建应用程序时,类AB都由静态加载器加载到内存中。 Both classes are in memory, along with the definitions of all their member methods.这两个类以及它们所有成员方法的定义都在内存中。

The standard way of deployment is to compile Java source code to bytecode.部署的标准方式是将 Java 源代码编译为字节码。 When the application is executed , the classes will be loaded.当应用程序被执行时,类将被加载。 There is no such thing as a “static loader”.没有“静态加载器”这样的东西。 There are different class loaders.有不同的类加载器。 When the class files are delivered on the class path, they will be loaded by the application class loader .当类文件在类路径上传递时,它们将由应用程序类加载器加载

When B is instantiated with the above statement, a memory space in the heap is allocated for that object b .B用上面的语句实例化时,堆中的内存空间被分配给那个对象b The methods m1() , m2() and m99() all have their definitions in this space of b .方法m1()m2()m99()b这个空间中都有它们的定义。

As said by Andreas , the method definitions are part of the JVM's class representation.正如Andreas 所说,方法定义是 JVM 类表示的一部分。 The object only contains a reference (pointer) to the class.该对象仅包含对类的引用(指针)。

These method definitions are only references to the method "templates" existing in the class definitions.这些方法定义只是对存在于类定义中的方法“模板”的引用。 In the class, these methods are sequences of operations-- parameterized operations if the method is executing on any parameter and/or global variable.在类中,这些方法是操作序列——如果方法在任何参数和/或全局变量上执行,则参数化操作。

The terms “definitions” and “templates” and the way you use them, are creating unnecessary confusion.术语“定义”和“模板”以及您使用它们的方式正在造成不必要的混淆。 The instruction sequences are part of a methods definition.指令序列是方法定义的一部分。 There is a reference from the object to these definitions, either indirectly via the already mentioned reference to the class, or directly via a table of method pointers, known as “vtable”, a widespread optimization.对象引用这些定义,或者通过已经提到的对类的引用间接引用,或者直接通过方法指针表(称为“vtable”),这是一种广泛的优化。

When one of the methods, say b.m99() is invoked at runtime, JRE goes to class definition of B to get that "template" (sequence of operations), looks up the current values of the fields of b , fills in that "template" with the current values of these field(s), also pushes these current values to the stackspace and runs the methods by executing these operations it found on the class definition.当其中一个方法,比如说b.m99()在运行时被调用时,JRE 转到B类定义以获取该“模板”(操作序列),查找b字段的当前值,并填写具有这些字段的当前值的“模板”还将这些当前值推送到堆栈空间,并通过执行在类定义中找到的这些操作来运行方法。

You should forget about that term “template”.你应该忘记“模板”这个词。 The method definition contains a sequence of executable instructions and the JVM will execute these instructions.方法定义包含一系列可执行指令,JVM 将执行这些指令。 For instance methods, a pointer to the object data becomes the implicit first argument.对于实例方法,指向对象数据的指针成为隐式的第一个参数。 No template will be filled with anything.任何模板都不会填充任何东西。

If the method is inherited from a superclass, Eg.如果该方法是从超类继承的,例如。 m2() above, the definition of that method in the class (the definition mentioned in Paragraph-A above) is itself a reference to the definition of m2() in class A.上面的m2() ,类中该方法的定义(上面 A 段中提到的定义)本身就是对类 A 中m2()定义的引用。

At runtime, when b.m2() is executed, JRE goes directly to class A to find that "template" for the low-level operations to execute.在运行时,当b.m2()被执行时,JRE 会直接进入A类以找到要执行的低级操作的“模板”。

This is an implementation detail, but hold your breath…这是一个实现细节,但请屏住呼吸……

These references to method definitions are checked at compile time and put into the bytecode.这些对方法定义的引用在编译时被检查并放入字节码中。 Eg.例如。 in the bytecode for the above case, class B has, a direct reference to method m2() of class A for method m2() it's inheriting from A .在字节码为上述的情况下,类B有,直接引用方法m2()类的A用于方法m2()它继承A

This is not, how Java works.这不是 Java 的工作原理。 In Java, the compiler will check for the presence of the invoked method, which succeeds as B inherits the method from A and its accessible, then, it will record an invocation as written in source code, for m2() invoked on B .在 Java 中,编译器将检查被调用的方法是否存在,当BA继承方法及其可访问性时,编译器会成功,然后,它将记录在源代码中编写的调用,用于在B调用的m2()

The fact that B inherits the method, is an implementation detail of B and allowed to change.这一事实B继承的方法中,是的实现细节B并使其变化。 A future version of B may override the method.的未来版本B可覆盖的方法。 If that happens, A.m2() may even get removed.如果发生这种情况, A.m2()甚至可能会被删除。 Or a class C may get introduced between A and B ( C extends A and B extends C ), which are all backwards compatible changes.或者可能会在AB之间引入类CC extends AB extends C ),它们都是向后兼容的更改。

But back to the previous section, at runtime , an implementation may utilize the knowledge about actual inheritance.但是回到上一节,在运行时,实现可能会利用有关实际继承的知识。 A JVM could search the super type hierarchy each time, a method is invoked, that would be valid but not very efficient.每次调用一个方法时,JVM 都可以搜索超类型层次结构,这可能是有效的,但效率不高。

A different strategy is to have the “vtable” mentioned above.一个不同的策略是拥有上面提到的“vtable”。 Such a table is created for each class when it is initialized, starting with a copy of all superclass methods, entries of overridden methods replaced, and newly declared methods at the end.每个类在初始化时都会创建一个这样的表,从所有超类方法的副本开始,被覆盖的方法的条目被替换,最后是新声明的方法。

So when an invocation instruction is executed the first time, it gets linked by determining the associated index in the vtable.因此,当第一次执行调用指令时,它通过确定 vtable 中的关联索引来链接。 Then, every invocation only needs to fetch the method pointer from the vtable of the object's actual class, without ever traversing the class hierarchy.然后,每次调用只需要从对象的实际类的 vtable 中获取方法指针,而无需遍历类层次结构。

That's still only the way, an interpreted or less optimized execution works.这仍然是唯一的方式,解释或优化程度较低的执行工作。 When the JVM decides to optimize the invoking code further, it may predict the actual target of the method invocation.当 JVM 决定进一步优化调用代码时,它可能会预测方法调用的实际目标。 There are two ways in your example您的示例中有两种方法

  • the JVM uses the knowledge that A.m2() has never overridden (it would have to remove such an optimization when a new class is loaded which does override the method) JVM 使用A.m2()从未覆盖的知识A.m2()当加载新类时它必须删除这样的优化,它确实覆盖了方法)

  • It analyzes the code path, to determine that for B b = new B(); b.m2();它分析代码路径,以确定对于B b = new B(); b.m2(); B b = new B(); b.m2(); the target is fixed as the result of new B() is always of type B , not a superclass and not a subclass.目标是固定的,因为new B()的结果总是B类型,不是超类也不是子类。

When the target is predicted, it can be inlined.当目标被预测时,它可以被内联。 Then, the optimized code simply does System.out.print(new Date());然后,优化后的代码简单地执行System.out.print(new Date()); and when there's no other use of the B instance, even the allocation may get eliminated.B实例没有其他用途时,甚至分配可能会被消除。

So what the JVM does at runtime, may be entirely different than what has written in source code.因此,JVM 在运行时所做的可能与源代码中编写的完全不同。 Only the perceivable result (the date is printed) will be the same.只有可感知的结果(打印日期)是相同的。

The methods m1() , m2() and m99() all have their definitions in this space of b .方法m1()m2()m99()b这个空间中都有它们的定义。

Incorrect.不正确。 The space allocated for b (the instance of B ) references the class itself, ie the space allocated for class B , where the method definitions are stored.b分配的空间( B的实例)引用类本身,即为类B分配的空间,方法定义存储在其中。

The space allocated for an object instance consists of an object header and the data of the instance, ie the values of the fields.为对象实例分配的空间由对象头和实例的数据(即字段的值)组成。 See eg What is in java object header for more information about the object header.有关对象头的更多信息,请参见例如java 对象头中的内容。

Eg.例如。 in the bytecode for the above case, class B has, a direct reference to method m2() of class A for method m2() it's inheriting from A .在字节码为上述的情况下,类B有,直接引用方法m2()类的A用于方法m2()它继承A

Incorrect.不正确。 The bytecode for class B knows nothing about method m2() . B类的字节码对方法m2()一无所知。

Remember, class A may be compiled separately from class B , so you can remove method m2 without recompiling class B .请记住,类A可以与类B分开编译,因此您可以删除方法m2而无需重新编译类B


UPDATE更新

From comment :来自评论

How then is it known what to execute when b.m2() is run?那么如何知道在运行b.m2()时要执行什么? I don't think JRE goes to the super-class of B , looks to see an m2() there, if no such method then goes to super-super class, ... Too inefficient in runtime.我不认为 JRE 进入B的超类,看起来在那里看到一个m2() ,如果没有这样的方法然后进入超超类,......运行时效率太低。 Must be a direct reference to m2() .必须是对m2()的直接引用。 m2() is a member of B -- even though inherited. m2()B的成员——即使是继承的。

As already stated in the answer, m2() is NOT a member of B .正如答案中已经说明的那样, m2()不是B的成员。 If you run the Java Disassembler, ie run javap B.class on the command-line, you'll see:如果您运行 Java 反汇编程序,即在命令行上运行javap B.class ,您将看到:

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

As you can see, the compiler has added the default constructor for you, but has not added any m2() method.如您所见,编译器为您添加了默认构造函数,但没有添加任何m2()方法。

Now create this class:现在创建这个类:

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

Then disassemble it with the -c switch, ie javap -c C.class :然后用-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
}

As you can see, the compiler generates an instruction to call B.m2() , even though we already saw that B.class doesn't know about m2() .如您所见,编译器生成一条指令来调用B.m2() ,即使我们已经看到B.class不知道m2()

This means that what you postulated is exactly what happens, ie the JVM needs to resolve the method to class A at runtime, by walking up the superclass chain.这意味着您所假设的正是发生的事情,即 JVM 需要在运行时通过沿超类链向上解析该方法到A类。

If m2() is removed from class A and recompiled, without recompiling class C , you will get NoSuchMethodError: 'void B.m2()' when running the code.如果从类A删除m2()并重新编译,而不重新编译类C ,则在运行代码时会得到NoSuchMethodError: 'void B.m2()'

It's all visible in the .class files if you use a disassembler like javap .如果您使用像javap这样的反汇编程序,它在 .class 文件中都是可见的。 The bytecode for class B does not contain method m2. B 类的字节码不包含方法 m2。 Further reading about Java handles inheritance here . 在此处进一步阅读有关 Java 处理继承的信息

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

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