Java virtual machine memory can be divided into program counters, Java virtual machine stacks, local method stacks, and heaps. Today, we’ll take a closer look at one of these areas, the Java Virtual Machine stack.

Just to clarify. The title of this article with a “Ctrip interviewer”, there is a title party suspected. However, some readers left comments in the last article saying that ctrip’s interviewer asked him about Java virtual machine memory, so I took the title from the topic.

From the word “brief encounter,” I gather that the reader failed at the interview question. Let’s just say that interviewers love to ask about Java virtual machines, because they can tell you a lot about a candidate’s real skills, so I’m going to write a few more articles on this topic in the hope of giving you a little more help

Java virtual machines use methods as the basic execution unit, and Stack frames are the basic data structures used to support Java virtual machines to call and execute methods. Each stack frame contains a local variable table, operand stack, dynamic linking, method return address, and some additional information (such as debugging, performance phone related information). These concepts have been mentioned in previous articles and have been briefly described, but I don’t think they are detailed enough, so this article will focus on these concepts in stack frames.

1) Local variation scale

The Local Variables Table is used to store Local Variables and method parameters in a method. When the Java source file is compiled into a class file, the maximum size of the local variable table is determined.

Let’s look at a piece of code like this.

public class LocalVaraiablesTable {
    private void write(int age) {
        String name = "Silent King II."; }}Copy the code

The write() method takes one parameter, age, and a local variable, name.

Then use Intellij IDEA jclasslib LocalVaraiablesTable check the compiled bytecode file. The class. You can see that in the Code attribute of the write() method, the Maximum local variables (the Maximum capacity of the local variables table) has a value of 3.

The maximum size of a local variable table should be 2, an age and a name. Why 3?

When a member method (non-static) is called, the 0th variable is actually the reference to the object that called the member method, the famous this. Calling the method write(18) actually calls write(this, 18).

Click on the Code property and look at LocalVaraiableTable to see the details.

The 0th is this, which is of type LocalVaraiablesTable; The first is the method parameter age, of type int; The second is the local variable name inside the method, of type String.

Of course, the size of the local variable scale is not the sum of all the local variables in the method, but is related to the type and scope of the variable. When the scope of a local variable ends, the place it occupies in the local variable table is replaced by the following local variable.

Take a look at this code.

public static void method(a) {
    / / 1.
    if (true) {
        / / 2.
        String name = "Silent King II.";
    }
    / / 3.
    if(true) {
        / / 4.
        int age = 18;
    }
    / / 5.
}
Copy the code
  • method()The local variable scale size of the method is 1. Since it is static, there is no need to add this as the first element of the local variable scale.
  • ②, the local variable has a name, and the size of the local variable scale changes to 1;
  • ③, the scope of the name variable ends;
  • (4) the local variable has an age, and the size of the local variable scale is 1.
  • ⑤ when the scope of the office age variable ends;

As for the scope of local variables, Effective Java recommends no. 57:

Minimizing the scope of local variables increases the readability and maintainability of your code, and reduces the likelihood of errors.

I have one more point to remind you of. To minimize the amount of memory consumed by stack frames, slots in the local variable table can be reused, as demonstrated by the method() method, which means that proper scope can improve program performance.

The capacity of the local variable table is in the smallest unit of slot, and a slot can hold a 32-bit data type (such as int, of course, the Java Virtual Machine Specification does not specify how much memory a slot should occupy, but I think it is easier to understand). Explicitly 64-bit data types like float and double occupy two slots right next to each other.

Take a look at the following code.

public void solt(a) {
    double d = 1.0;
    int i = 1;
}
Copy the code

As you can see from jclasslib, the solt() method’s Maximum local variables have a value of 4.

Why is this equal to 4? There are only three “this” s.

If you look at LocalVaraiableTable, the subscript of variable I is 3, which means variable D occupies two slots.

2) Operand stack

Like the local variable table, the maximum depth of the Operand Stack is determined at compile time, written into the maximum Stack size of the Code property. When a method is first executed, the operand stack is empty. During the execution of the method, various bytecode instructions write and fetch data into and out of the operand stack.

Take a look at this code.

public class OperandStack {
    public void test(a) {
        add(1.2);
    }

    private int add(int a, int b) {
        returna + b; }}Copy the code

The OperandStack class has two methods. The test() method calls add(), passing two arguments. The maximum stack size of the test() method is 3.

This is because a call to a member method pushes this and all of its arguments onto the stack, and when the call is complete, this and all of its arguments are pushed off the stack. You can view the corresponding Bytecode instructions in the Bytecode panel.

  • Aload_0 is used to load a variable of reference type with subscript 0 in the local variable table, namely this, into the operand stack;
  • Iconst_1 is used to load the integer 1 into the operand stack;
  • Iconst_2 is used to load the integer 2 into the operand stack;
  • Invokevirtual is the member method used to invoke an object.
  • Pop is used to push the top value off the stack;
  • Return is the return instruction of the void method.

Look again at the bytecode instructions for the add() method.

  • Iload_1 is used to load variables of type int with subscript 1 in the local variable table onto the operand stack (subscript 0 is this);
  • Iload_2 is used to load variables of type int with subscript 2 in the local variable table onto the operand stack;
  • Iadd is used for int addition;
  • Ireturn indicates the instruction to return a method whose return value is int.

The data type in the operand must match the bytecode instruction. For example, the iADD instruction above can only be used to add integer data. The two items at the top of the stack must be of type int. A long and a double cannot be added to iadd.

3) Dynamic linking

Each stack frame contains a reference to a method in the runtime constant pool to which that stack frame belongs. This reference is held to support Dynamic Linking during method invocation.

Take a look at this code.

public class DynamicLinking {
    static abstract class Human {
       protected abstract void sayHello(a);
    }
    
    static class Man extends Human {
        @Override
        protected void sayHello(a) {
            System.out.println("It's not a crime for a man to cry."); }}static class Woman extends Human {
        @Override
        protected void sayHello(a) {
            System.out.println("The woman under the mountain is a tiger."); }}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

If you’re familiar with Java rewriting, you should be able to understand what this code means. The Man and Woman classes inherit from the Human class and override the sayHello() method. Take a look at the results:

It's not a sin for a man to cry. The woman under the mountain is a tiger. The woman under the mountain is a tigerCopy the code

Human refers to man, and woman refers to woman. Then man points to the new Woman object.

From the point of view of object-oriented programming, from the point of view of polymorphism, we have a good understanding of the results, but from the point of view of the Java virtual machine, how does it decide which method to call man and woman?

Take a look at the bytecode instructions for the main method using jclasslib.

  • Line 1: The new instruction creates a Man object and pushes the memory address of the object onto the stack.
  • Line 2: The DUP instruction makes a copy of the value at the top of the stack and pushes it to the top. Because the following directive invokespecial consumes a reference to the current class, a copy is required.
  • Line 3: The Invokespecial directive is used to call the constructor for initialization.
  • Line 4: astore_1, where the Java virtual machine pops a reference to the Man object from the top of the stack and stores it in the local variable Man with subscript 1.
  • The instructions in lines 5, 6, 7, and 8 are similar to those in lines 1, 2, 3, and 4, except for the Woman object.
  • Line 9: the aload_1 instruction pushes the local variable man onto the operand stack.
  • Line 10: The Invokevirtual directive invokes the member methods of the objectsayHello()Notice that the object type iscom/itwanger/jvm/DynamicLinking$Human.
  • Line 11: aload_2 pushes the local variable woman onto the operand stack.
  • Line 12 and line 10.

Note that the bytecodes for man.sayhello () (line 10) and woman.sayhello () (line 12) are identical from a bytecode point of view, but as we all know, the two instructions end up executing different target methods.

What’s going on?

Start with the invokevirtual directive to see how it implements polymorphism. According to the Java Virtual Machine Specification, the invokevirtual directive is resolved at runtime in the following steps:

Find the actual type of the object pointed to by the element at the top of the operand stack. Call it C.

If a method in type C matches the descriptor in the constant pool, the access permission check is performed. If the method passes, the direct reference is returned, and the search ends. Otherwise returns the Java. Lang. IllegalAccessError anomalies. ③ Otherwise, search and verify the second step of each parent class of C from bottom to last time according to the inheritance relationship. (4), if didn’t find the right way, it throws the Java. Lang. AbstractMethodError anomalies.

That is, the Invokevirtual directive determines the actual type of the runtime in the first step, so instead of resolving symbolic references to methods in the constant pool to direct references, the invokevirtual directive selects method versions based on the actual type of method recipients. This process is the essence of Java rewriting. We call this process of determining the version of method execution at run time based on the actual type dynamic chaining.

4) The method returns the address

When a method is executed, there are only two ways to exit the method:

  • If the method exits normally, a return value may be passed to the upper level method caller. Whether or not the method has a return value and the type of the return value depends on the instruction that the method returns. There are others, lreturn for long, freturn for float, dreturn for double, and areturn for reference types.

  • An exception exit, in which a method encounters an exception during execution and is not properly handled, does not return any value to its upper callers.

Either way, after the method exits, it must return to where it was when the method was first called before the program can continue executing. In general, the value of the PC counter is used as the return address when the method exits normally, and it is likely to be stored in the stack frame, but not in the case of an exception exit.

The process of method exit is essentially the same as removing the current frame from the stack, so the following operations may be performed: restoring the local variable table and operand stack of the upper method, pushing the return value (if any) into the operand stack of the caller’s stack, adjusting the value of the PC counter, finding the next instruction to execute, and so on.


The above content is from teacher Zhou Zhiming’s “In-depth Understanding of Java Virtual Machine”, and good friend Zhang Ya’s “In-depth Understanding of JVM bytecode”. These two books are highly recommended.

Beginners at the beginning of learning Java virtual machine may feel very boring, very difficult to understand, but with a certain amount of experience, to learn this knowledge will have a sense of enlightenment. Of course, the Java virtual machine is a must-learn, because performance optimization, job interviews, and even programming skills are all very important.

Mentioned one of the four books “the university,” opens with a concept called “new-confucianism”, which means that by studying the principle behind the things to acquire knowledge, go deep into deep behind the Java virtual machine, bytecode to analyze the structure and principle of Java, can let us become more confident, whole body diffuse light on a “technical” ~


I recently spent nearly a week compiling a Java version of the brush problem notes, a total of 300 problem solutions!

Illustrated, screenshot below, not only dry problem solving code, many questions give a variety of ideas to solve the problem, will really improve the happiness index of everyone to brush the problem ~

After eating 300 LeetCode questions, I am so fat that I am about to explode! with Java

I am silent king two, a praise, let us become a master together, 😁!