From the perspective of method call, the features of static multi dispatch and dynamic single dispatch of Java language

Posted by poizn on Fri, 17 Dec 2021 22:05:33 +0100

Method call

For virtual machines, method invocation is not only to execute a method, but also to determine the target method to be executed. For example, in the case of replication, it is necessary to know whether to call the method of the parent class or the method of the child class. In the case of overloading, select one of multiple methods with the same name for execution. When we slowly open the black box of the virtual machine, the general execution logic of the virtual machine is clear at a glance.

Resolve call

In the bytecode of the class file, because no memory is allocated, the calling methods are all symbolic references representing dependencies. In the parsing stage of class loading, the symbolic references will be converted into direct references (determining the memory address). This can be known during compilation and will not change during operation. It is called parsing, but it is consistent with this method

  • Static method, private method, method modified by final, instance constructor. Their common law is that they can not be copied. This is also called non virtual method. On the contrary, the other common methods are virtual methods.

As mentioned above, parsing call is a static process, and there is no need to confirm the method call version when the program is running; The other is dispatch call, which may be static or dynamic. Replication and overloading of the most common features of Java can be explained by dispatch call.

Dispatch call - static dispatch

Let's talk about static dispatch first and illustrate it with a code example:

package overload;


import java.util.Random;

public class OverLoad {

  static abstract class Person {

  }

  static class Man extends Person {

  }

  static class Woman extends Person {

  }

  public void sayHello(Person person) {
    System.out.println("person hello");
  }

  public void sayHello(Man person) {
    System.out.println("man hello");
  }

  public void sayHello(Woman person) {
    System.out.println("woman hello");
  }


  public static void main(String[] args) {
    Person person = new Random().nextBoolean() ? new Man() : new Woman();
    OverLoad overLoad = new OverLoad();
    overLoad.sayHello(person);
  }

}

Operation results:

person hello

Person is an abstract class, and we deliberately randomize the implementation object, that is, it may be initialized to Man or Woman. The three overloaded methods only follow the sayhello (person) version. Let's review the definition of overload a little-
Which overload version to use depends on the type and number of method parameters. In this example, the number of parameters is the same. For parameter types, during compilation, you don't know what type of object this reference refers to (Man and Woman are random),
It can be clearly known only when the program is running, and the overload is determined according to the static type (Person) of the parameter rather than the actual type (Man or Woman), so the sayHello(Person person) version will be used in the end.

All dispatch actions that determine the execution version of the method by static type are called static dispatch.

  • The most typical implementation of static dispatch is method overloading.
  • Static dispatch occurs during the compilation phase.

Dispatch call - Dynamic Dispatch

Dynamic dispatch is mostly used for rewriting, or is it explained in Code:

package overload;


import java.util.Random;

public class Override {

  static abstract class Person {
    int num = 1;

    public void sayHello() {
      System.out.println("person hello");
    }
  }

  static class Man extends Person {
    @java.lang.Override
    public void sayHello() {
      System.out.println("man hello");
    }
  }

  static class Woman extends Person {
  }

  public static void main(String[] args) {
    Person person = new Random().nextBoolean() ? new Man() : new Woman();
    person.sayHello();
  }

}

The result is random. If Man is initialized, man hello will be output, and if Woman is person hello. The result is not unexpected, but how does the Java virtual machine determine which method to call? Dependent static dispatch
Mechanism or parsing call mechanism is impossible. It is impossible to know which object is instantiated during static dispatch compilation. Parsing call only works on non virtual methods and is not applicable to replication methods. At this time, dynamic dispatch comes in handy. At the bytecode level, sayHello
Method is actually executed using the invokevirtual instruction

...
#9 = Methodref          #15.#32        // overload/Override$Person.sayHello:()V
... 
32: invokevirtual #9                  // Method overload/Override$Person.sayHello:()V
...

The invokevirtual instruction is executed in the following steps:

  1. First find the actual object pointed to by the reference and record it as C
  2. Start the process of finding methods. If a matching method is found in C, the direct reference of this method will be returned. The lookup process ends.
  3. Otherwise, find the parent class of C in step 2 from bottom to top according to the inheritance relationship.
  4. If the appropriate method is never found, throw Java Lang. abstractmethoderror exception.

This is the most basic process of dynamic dispatch. The key premise is to get the actual object type when the program is running, and then find it from bottom to top.

Then, if we understand that dynamic dispatch operates through invokevirtual, we can also deduce that field calls have no polymorphism. Or look at a piece of code:

package overload;


import java.util.Random;

public class Override_2 {

  static abstract class Person {

    public int num = 1;

    public Person() {
      sayHello();
    }

    public void sayHello() {
      System.out.println("person hello");
    }
  }

  static class Man extends Person {
    public int num = 2;

    public Man() {
      sayHello();
    }

    @java.lang.Override
    public void sayHello() {
      System.out.println("man hello");
    }
  }

  static class Woman extends Person {
    public int num = 3;
  }

  public static void main(String[] args) {
    Person person = new Man();
    System.out.println(person.num);
  }

}

Operation results:

man hello
man hello
1

In the first line, man hello executes the constructor of the parent class, but sayHello() is a virtual method. According to the rules of dynamic dispatch, it is found from the method of the actual execution object, so the sayHello() method of Man object is executed.

The second line executes the constructor of the Man object, which in turn executes the sayHello() method.

The third line directly outputs the num field of Person because the Java field has no polymorphism and the reference is of type Person.

Single dispatch and multiple dispatch

After we understand dynamic dispatch and static dispatch, we introduce the concepts of single dispatch and multi dispatch.

  • When we review and analyze overloading, we use static dispatch, which considers two factors: the executor of the method (static type) and the parameter type of the method. The final product of this selection result is invokevirtual
    Instruction whose parameter is the symbol reference pointed to in the constant pool. Because the selection is based on two dimensions (parcel quantity), the static dispatch of Java language belongs to multi dispatch type.
  • Then, the running phase is the process of dynamic dispatch. At this time, the parameter type has been determined during static dispatch. The only thing that needs to be determined is the executor of the method. Therefore, the dynamic dispatch of Java language belongs to single dispatch type.

To sum up, Java is a static multi dispatch and dynamic single dispatch language.

Implementation of dynamic dispatch

After knowing what dynamic dispatch does, we will also guess that the virtual machine may search for the appropriate target method from the caller's method metadata. However, for performance reasons, the virtual machine will establish a virtual method table in the method area and use the virtual method table index to replace the metadata search. The virtual method table will store the actual memory address of each method.

If an object, as a subclass, does not duplicate the method of the parent class, the memory address of the corresponding method in the virtual method table will directly reuse the address of the parent class (without searching the metadata of the parent class to improve performance); on the contrary, if the method of the parent class is copied, the address of the subclass is the address of its own implementation method.

Data integration

  • This chapter integrates the content of in-depth understanding of Java virtual machine (3rd Edition). If you want to learn systematically, you can read this book directly.
  • The code for this article has been placed in the test overload, test override folder (from knowledge-ship).

Topics: Java jvm