This is the 21st day of my participation in the August Text Challenge.More challenges in August

preface

Most of the articles on the JVM have already been covered and are coming to the end of this series. This article will cover how the JVM does overloading and overwriting, as well as the internals of stack frames, which are very important and some of the most frequently asked questions in an interview.

An overview of the

  1. Understand the internal structure of stack frame and what each component does and is responsible for

  2. Understand the execution process of the dispatch key command: invokeVirtual command

  3. Know what method dispatch is and why Java uses static multiple dispatch and dynamic single dispatch

  4. See how overloading and overwriting are implemented in the JVM

Internal structure of stack frame

Let’s talk about the internal structure of the stack frame. The stack frame is the basic component unit existing in the virtual machine stack, or can be considered as the essence of calling methods, and the stack frame stores the data needed by the virtual machine bytecode instructions. Let’s first look at the contents of the stack frame:

  • Local variable scale
  • The operand stack
  • Dynamic link
  • Method return address
  • Additional information

It can be seen that the contents of a frame are quite a lot. Here we describe the contents of each “variable” in order:

Local variable scale

The local variable scale can be simply understood as the method parameters of the method defined by us, but it not only contains these contents, but also the parameters of local variables in the method. In general, the local variable scale stores all parameters related to the method.

Note that there is a property called Code in the property table, which is the actual “container” for user Code. In this “container”, a field called **max_locals ** is used to determine the actual length of method arguments.

Local variables that use the capacity of the “container”, called “variable slot” as the basic unit in order to guarantee both 32-bit and 64 – bit operating system compatibility, the variable groove will according to the actual operating system perform “alignment padding” operations, but it also raises the question, is the waste of space, so in order to solve the problem of the alignment padding, Java followed with a “compressed pointer” technique to address…..

The following are some characteristics of the variable slots of the local variable table:

  • Use index positioning, 32-bit with a single N pointing, and 64-bit with n and n+1 adjacent positions
  • The method call uses slot 0 to store the bit-passing method for this reference, which can be said to start at 1 instead of 0.
  • Local variables, unlike class variables, cannot be used without initialization
  • Used variable slots are reused in variable scope
  • Variable slots cannot be used without initialization.

There is a side effect to the fourth point: it affects garbage collection behavior. Because the local variable table does not have read and write reuse, large objects may not be recycled. For example, the following code will still be in the placeholder scope after the scope ends, and will not be reclaimed.

public static void main(String[] args)(a) {
  byte[] placeholder = new byte[64 * 1024 * 1024]; 
  System.gc();
}
Copy the code

In this case, we sometimes use read/write reuse local variable tables to manipulate data and use obj = NULL to destroy objects in advance. Can we use this method to help the virtual machine recognize objects as garbage more quickly? Big mistake, this method may make you feel faster to reclaim memory, but is actually meaningless. Sometimes it even affects the optimization of the virtual machine itself

The operand stack

Operand stack is a back in back out stack structure, as the name is used for numerical calculation in the method, by pushing the stack and out of the way to calculate the result of the variable, the operand stack is the same as local variable table, according to the size of different bits occupied by different.

Dynamic link

Dynamic link means that the stack frame keeps pointing to the running constant stack frame belongs to the method reference. Its existence significance is to support the method call process of dynamic link, which may not be understood here. In fact, dynamic link mainly does the following two things:

  1. There are symbolic references to method directives in the class constant pool

  2. Method calls take a constant pool reference as an argument

Method return address

The return address of a method is related to the return instruction of a method. Methods can return in two ways:

  1. Returns the bytecode instruction (note that the void method is automatically added at the end)

  2. Exception interrupt call exception exit return value, while the return address is handled by the different handler

The following operations are possible for exiting (this operation is actually determined by the virtual machine and implemented differently by different virtual machines) :

  1. Restore upper-layer method local variables and operation stack

  2. The return value is pushed onto the stack

There’s some extra information at the end, but it’s not important and I’m going to skip it.

Methods:

Here we return to the “parsing” step of class loading to explain how specific instructions are implemented. The code generated when the calling program is compiled is called parsing, and the following five instructions are responsible for converting symbolic references to direct references.

  • Invokestatic: used to invokestatic methods.

  • Invokespecial: Used to call methods in instance constructor () methods, private methods, and parent classes. · invokevirt ual. Used to call all virtual methods.

  • Invokeinterface: Used to invoke the interface method, which determines an object implementing the interface at run time.

  • Invokedynamic: The method referenced by the call point qualifier is dynamically resolved at run time and then executed. The dispatch logic of the invokedy Namic instruction is fixed in the Java VIRTUAL machine, while the dispatch logic of the Invokedy Namic instruction is determined by the user-set guidance method.

Now that we understand the parsed instructions, what are virtual methods and what are non-virtual methods?

Virtual methods and non-virtual methods

Virtual method refers to the method that can directly resolve symbolic reference to direct reference in the parsing stage. It includes invokestatic and InvokESP ecial instructions. It contains five kinds in total: static method, private method, instance constructor, parent class method and final modification method.

A non-virtual method is one that invokes a generated instruction or method in addition to the one mentioned above.

In addition to these two methods, there is another method called dispatch, which includes dynamic dispatch and static dispatch. Dynamic dispatch and static dispatch are divided into single dispatch and multiple dispatch, so finally there are the following:

  • Static multiple dispatch

  • Static single dispatch

  • Dynamic multiple dispatch

  • Dynamic single dispatch

The dispatch

The following are the modes of dispatch, including dynamic dispatch and static dispatch. According to the static dispatch and dynamic dispatch, it is divided into multiple dispatch and single dispatch. The following are explained according to the four categories above:

Static dispatching

To explain static dispatch, the following code is presented:

/** * method static dispatch demo *@author zzm */
public class StaticDispatch {
  static abstract class Human {}static class Man extends Human {}static class Woman extends Human {}public void sayHello(Human guy) { 
    System.out.println("hello,guy!");
  }
  
  public void sayHello(Man guy) { 
    System.out.println("hello,gentleman!");
  }
  
  public void sayHello(Woman guy) { 
    System.out.println("hello,lady!");
  }
  
  public static void main(String[] args) { 
    Human man = new Man();
    Human woman = new Woman();
    StaticDispatch sr = newStaticDispatch(); sr.sayHello(man); sr.sayHello(woman); }}Copy the code

The result here is two hello,guy! Because the object reference passed in at call time is determined at compile time, it can be considered a static allocation, which means that the method is determined directly at run time. ** All dispatch methods that rely on static types can be called static dispatch. ** This also indicates that static assignment of methods is done at compile time and is not performed by the virtual machine, since static typing is determined prior to execution and final static assignment is also key for overloaded implementation.

There is a further explanation of the reloading order, which is of course meaningless except for the interview:

package org.fenixsoft.polymorphic; 
public class Overload {
  public static void sayHello(Object arg) { System.out.println("hello Object");
  }
  public static void sayHello(int arg) { System.out.println("hello int");
  }
  public static void sayHello(long arg) { System.out.println("hello long");
  }
  public static void sayHello(Character arg) { System.out.println("hello Character");
  }
  public static void sayHello(char arg) { System.out.println("hello char");
  }
  public static void sayHello(char. arg) { System.out.println("hello char ...");
  }
  public static void sayHello(Serializable arg) { System.out.println("hello Serializable");
  }
  public static void main(String[] args) { sayHello('a'); }}Copy the code

Dynamic dispatch

Dynamic dispatch involves an important operation: rewrite. In the case of rewrite, we rewrite the case of static dispatch. Here is the specific code:

 public class Dynamic{
  static abstract class Human {
    protected abstract void sayHello(a);
  }
  static class Man extends Human { 
    @Override
    protected void sayHello(a) { 
      System.out.println("man say hello"); }}static class Woman extends Human { 
    @Override
    protected void sayHello(a) { 
      System.out.println("woman say hello"); }}public static void main(String[] args) { 
  Human man = new Man();
    Human woman = new Woman(); man.sayHello();
    woman.sayHello(); 
    man = newWoman(); man.sayHello(); }}Copy the code

The result of running this code is believed to be able to answer. Can see from this code, due to the nature of polymorphism, the implementation of the methods could no longer be in the compiler can know, because here if according to the static compilation a subclass, there will be two the same way at this time is the time when the dynamic dispatch work, as to why the JVM can know whether run time execution which a method, Invokevirtual executes the following instructions:

Invokevirtual instruction execution process

The runtime resolution process of invokevirtual instructions is roughly divided into the following steps:

1) Find the actual type of the object pointed to by the first element at the top of the operand stack, and call it C.

2) If a method is found in type C that matches both the descriptor and the simple name in the constant, the access permission is checked. If the method passes, the direct reference of the method is returned, and the search process ends. Not through the return Java. Lang. IllegalAccessError anomalies.

3) Otherwise, search and verify the second step of each parent class of C from bottom to top according to the inheritance relationship.

4) if you never find the right way, it throws the Java. Lang. AbstractMethodError anomalies.

Dynamic dispatch requires not only finding methods, but also checking the actual type of the object in the first step, rather than simply replacing symbolic references with dynamic references. The dispatch process in which the runtime determines the version of method execution based on the real-world type is called dynamic dispatch.

The above implementation proves this statement: Invokevirtual is the fundamental reason Java polymorphism can be implemented, and it is also the reason fields do not participate in polymorphism.

Single dispatch and multiple dispatch

Again, use the code from the original book:

/** * single dispatch, multiple dispatch demo *@author zzm
     */
    public class Dispatch {
        static class QQ {}static class _360 {}public static class Father {
            public void hardChoice(QQ arg) {
                System.out.println("father choose qq");
            }

            public void hardChoice(_360 arg) {
                System.out.println("father choose 360"); }}public static class Son extends Father {
            public void hardChoice(QQ arg) {
                System.out.println("son choose qq");
            }

            public void hardChoice(_360 arg) {
                System.out.println("son choose 360"); }}public static void main(String[] args) {
            Father father = new Father();
            Father son = new Son();
            father.hardChoice(new_360 ()); son.hardChoice(new QQ());
        }
Copy the code

Let’s look at multiple dispatch, you can see as defined here the QQ and 360 two objects, these two object inside the parent and child classes as a parameter to dispatch action, we have said before, due to the static dispatching is at compile time has finished, so in the judgement methods and types of judgment is to call parents or children, Then judge which a concrete object which called parameter method, this is accomplished by instruction invokevir and can determine the multiple selection (selection type and the type of method arguments), so the dispatch mode into multiple dispatch mode, at the same time to dispatch in the case of static, so the static allocation of Java is multiple dispatch.

We are looking at single dispatch. Since static is multi-dispatch, dynamic must be single dispatch. Why? Because when method always need to identify a specific method of entry, and after rewriting, subclass parameter type is what, you don’t care about which one he only CARES about execution of the object of specific methods, the only way can determine the caller, also can saying is method of the receiver is the parent class object or a subclass object. So dynamic dispatch in Java is determined to be single-dispatch, because there will ultimately be only one recipient of the actual type.

Static multiple dispatch is the actual entry point to a method call based on the compiler’s multiple choices of parameters and types. The actual type can be determined directly at compile time. Dynamic single dispatch determines which caller’s method is actually called based on which caller is actually called at run time, and the actual type is determined at run time.

In simpler terms, static multiple dispatch determines the entry at compile time, while dynamic single dispatch determines the caller at run time.

Dynamic assignment of VMS

The dynamic dispatch implementation of virtual machines can be understood directly from the above description.

The virtual method table in the JVM holds the actual entry address of each method. If a method is not overridden in a subclass, the address entry in the virtual method table of the subclass is identical to the address entry of the same method in the parent class, pointing to the implementation entry of the parent class. If a subclass overrides this method, the address in the subclass’s virtual method table is replaced with the entry address pointing to the subclass’s implementation version.

conclusion

In this article we have looked at the structure of stack frames, as well as the details of dispatching, dynamic dispatching and static dispatching, and how Java implements overloading and overwriting in terms of single dispatching and multiple dispatching. Understanding the instructions of invokeVirtual is very important for understanding overloading and overwriting, because both dynamic single-dispatching and static multi-dispatching, They are essentially using the instructions of invokeVirtual.

Write in the last

In this section, we have taken our understanding of dispatching and framing to a new level, and the next article will cover how the JVM implements dynamic languages, which is also important.