It will take about 12.8 minutes to read this article.

The foreword 0.

The JVM is able to execute Java bytecode across computer architectures, meaning that Java can execute across platforms, mainly because the JVM hides differences between software or hardware associated with each computer platform, leaving platform-specific coupling to be implemented by the JVM provider.

This article will introduce the following:

  1. Introduces the overall design architecture of the JVM
  2. How does the JVM’s execution engine work
  3. How does the execution engine simulate the execution of JVM instructions

1. JVM architecture

In the previous sections, you’ve covered Class classes in depth and looked briefly at how to load Java bytecode into the JVM through the classloader. Let’s take a look at how the ARCHITECTURE of the JVM is designed, taking a macro view to give you an idea of the most basic JVM architecture and working patterns.

Let’s start with the following question

  • What’s the difference between a JVM and a physical machine?

1.1 what is meant by the JVM

The full name of JVM is Java Virtual Machine, Java Virtual Machine, it simulates a computer to achieve the computing functions of a computer.

Let’s take a look at how a real computer can compute.

Computing as the center of the computer architecture can be divided into the following parts:

  • Instruction set, a collection of commands in a machine language that a computer can recognize.
  • A computing unit, a functional module that identifies and controls the execution of instructions.
  • Address mode, the number of bits, minimum address and maximum address range of the address, and the rules for the operation of the address.
  • The definition of registers, including the definition, number and usage of operand registers, indexing registers, control registers, etc.
  • Storage unit: a unit capable of storing operands and preserving the structure of operations, such as kernel cache, memory, and disk.

The above sections are most closely related to what we call code execution, but the instruction set is defined in more detail below.

What is an instruction set? What does it do?

Instruction set is a set of instructions used to calculate and control the computer system in the CPU, each new TYPE of CPU in the design of a series of instructions with other hardware circuits. The advanced instruction set is also related to the performance of CPU, is an important symbol of CPU performance.

What is the relationship between instruction sets and assembly language?

An instruction set is machine code that can be recognized directly by the machine, that is, it must be stored in the computer in binary format.

Assembly language is an instruction that can be recognized by people. Assembly language corresponds to machine instruction in order and logic. That is, assembly language is designed to make it easier to remember mnemonics for machine instructions.

How does the instruction set relate to the CPU architecture? Are different CPU instruction sets compatible? Does the CPU architecture affect the instruction set?

Assembly language is the direct operation of registers and segments of the command, register and segment chips are part of the architecture, so different chip architecture design will correspond to different machine instruction set. But different chip manufacturers often use compatible with other in a way that is compatible with different instruction set architecture, because the monopoly of the operating system, Microsoft operating system is to be calculated in the management of the real entrance, almost all programs should pass the operating system calls, if the operating system does not support a chip set of instructions, the user program is impossible to perform.

How to check the different instruction sets supported by the CPU?

Use cpu-Z software to see which instruction sets the CPU supports, see the following figure:

On the instruction set line, we can see that the current CPU supports 11 instruction sets.


Having said that, let’s get back to the JVM and think about the question we raised earlier: how is the JVM different from the physical machine?

There are basically the following points:

  • An abstract Specification that defines what a JVM is and what its components are, as detailed in The JavaTM Virtual Machine Specification.
  • A concrete implementation, the so-called concrete implementation is different vendors according to this abstract specification with software or software and hardware combination in the same or different platform concrete implementation.
  • A running instance, when used to run a Java program, is a running instance, and each running Java program is a JVM instance.

A JVM, like a physical machine, must have a proper set of instructions that can be parsed and executed by the JVM. This set of instructions is called the JVM bytecode instruction set. Any bytecode that conforms to the class file specification can be executed by the JVM.

1.2 Details of THE JVM architecture

Let’s take a look at what components a JVM needs in addition to the instruction set. As you can see in the figure below, the STRUCTURE of the JVM basically consists of just four parts.

  • A classloader that loads the required classes into the JVM either when the JVM starts or when the class is running.
  • Execution engine, which is responsible for executing the bytecode execution contained in the class file when executing the engine’s tasks, equivalent to the CPU of the actual machine.
  • Memory area, the memory is divided into several areas to simulate the storage, recording and scheduling function module on the actual machine, such as the register of various functions on the actual machine or the recorder of THE PC pointer.
  • Local method call, code that calls a local method implemented in C or C++ returns the result.

Describe the four parts in detail:

  1. Class loaders:

    How ClassLoader works will be described in more detail in a later article. It is important to note that each JVM loaded type is represented by an instance of the corresponding java.lang.Class Class. This instance can uniquely represent the JVM loaded Class, which is stored in the Java heap like any other Class.

  2. Execution engine:

    The execution engine is the core part of the JVM. Its function is to parse JVM bytecode instructions and get the execution results. The Java Virtual Machine Specification defines in detail what the execution engine should process and get for each bytecode instruction it encounters. But there is no specification of how or how the execution engine should process this result. The implementation of the execution engine depends on the JVM vendor, whether it is interpreted directly, JIT (just-in-time even if compiled) and native code, or region-on-chip execution. Therefore, the specific implementation of the execution engine has a great space to play, such as SUN’s hotspot is based on the stack execution engine, and Google’s Dalvik is based on the register execution engine.

    An execution engine is a process that executes a code, and the code is contained in the method body, so the execution engine is essentially a process that executes a string of methods. Corresponding to an execution process in the operating system, a Java thread can have multiple execution processes at the same time. In this sense, each Java thread is an instance of an execution engine, and there can be multiple execution engines running simultaneously within a JVM instance, either executing the user’s program or executing programs within the JVM (such as the Java garbage collector).

  3. Java Memory Management

    When executing a program, the execution engine needs to store some things, such as the operands required by the opcode, and the execution result of the opcode needs to be saved. The bytecode of the class class and the object of the class need to be prepared before the execution engine executes it. From the first diagram, you can see that a JVM instance has a method area, Java heap, Java stack, PC registers, and local method area. The method area and the Java heap are shared by all threads, which means they can be accessed by all execution engine instances. When each new execution engine instance is created, a Java stack and a PC register are created for the execution engine. If a Java method is currently executing, the current Java stack stores the state of method calls in that thread. Including method parameters, method local variables, method return values and operator results. The PC register points to the next instruction to be executed.

  1. Local method call

    Local method calls are stored in the local method call stack or in an area of memory in a particular implementation.

2. Working mechanism of JVM

Following a brief look at the basic structure of the JVM, let’s take a look at how the JVM executes bytecode commands and how the execution engine works.

2.1 How does the machine execute code

Before looking at how the JVM’s execution engine works, let’s take a look at how a normal physical machine program executes. Let’s take a look at the picture below.

The computer only accepts machine instructions. High-level languages must be compiled into machine instructions by a compiler before they can be executed by the computer, so there must be a translation process from high-level languages to machine languages. We know that machine languages are closely related to hardware platforms, and compilers solve the problem of coupling high-level languages with hardware through compilation. Different hardware platforms need different compilers, and the hardware platforms have been replaced by the next level of software platform, the operating system. So the C compiler shown above is different for different operating systems. Of course, there are many compilers from different manufacturers that have little to do with the operating system, just implementation differences.

Usually a program goes through the following stages from writing to executing:

Except for the source code and the final executable program, all intermediate links are completed by the modern sense of the compiler.

For example, to install a software on Linux, you need to go through four steps: confrgure, make, make install, and make clean.

Configure selects the appropriate compiler for the program under the current operating system environment to compile the program code, that is, selects the appropriate compiler and some environment parameters for the program code.

Make compiles program code into an executable object file.

Make install Install the compiled executable file to the specified or default installation directory of the OPERATING system.

Make clean Deletes directories or files created temporarily during compilation.

The compiler we’re talking about usually translates from a high-level language to the target machine language, which is a low-level language. There are also compilers that edit high-level languages to high-level languages, compile high-level languages to virtual machine target languages (the Java compiler), and translate low-level languages to high-level languages (decompilation).

How do you get a machine (physical or virtual) to execute code?

The most basic elements of the instruction set: addition, subtraction, multiplication, mod, etc. These operations can be further decomposed into binary operations: and, or, xOR, etc. These operations are completed by instruction, and the core purpose of instruction is to determine the type of operation (opcodes) and data (operands) needed for operation, as well as where to obtain the operands from (registers or stacks), and where to store the results of operation (registers or stacks). This different mode of operation will be divided into instructions: one instruction address, two instruction address, three instruction address and zero instruction address and other N address instruction. These instruction sets have corresponding architectural implementations, such as register-based architectural implementations or stack-based architectural implementations, where register-based or stack-based refers to how operands in an instruction are fetched.

2.2 Why do JVMS choose stack-based architectures

The JVM executes bytecode instructions based on a stack architecture, in which all operands must be pushed onto the stack, and then a number of elements are selected from the top of the stack to be evaluated, pushing the result of the calculation onto the stack.

JVM operand can be stored in a local variable in each stack frame set, each method invocation will be allocated a set of local variables to this method, the local variable set have determined at compile time, so the operand stack can be directly is constant stack or remove a variable from the local variable pressure into the stack.

If both operands are in a local variable, then an addition operation has five stack operations. Two operands are pushed from the local variable (two pushes). Two operands are pushed off the stack for addition (twice), and the result is pushed to the top of the stack (once). If it is register-based, you generally only need to store the two operands in the register for addition operation and then store the result in one of the registers. There is no need to move so much data. So why are JVMS designed based on stacks?

  1. JVMS are designed to be platform independent

    To be platform-independent, ensure that Java code can run on machines with few or no registers.

  2. The JVM optimizes code better

    For Java, the JVM can be used either as a connector (dynamic) or as an optimizer. This stack-centric architecture brings together run-time optimization work with execution engines that perform just-in-time compilation or adaptive optimization to better optimize the execution of Java bytecode instructions.

  3. For the compactness of the instructions

    Opcodes can be as small as one byte, to make the compiled class file as compact as possible and to improve the efficiency of the bytecode transfer over the network.

2.3 Architecture design of the execution engine

Now that you know why Java is stacked, take a closer look at how the JVM designs Java’s execution parts, as shown in the figure below.

Whenever a new thread is created, the JVM creates a Java stack for that thread and assigns it a PC register that points to the thread’s first line of executable code. Each time a new method is called, a new stack frame data structure is created on the stack. This stack frame retains some meta information about the method, such as local variables defined in the method, some support for constant pool parsing, normal method return, and exception handling mechanisms.

The JVM may need to use constants in the constant pool to invoke certain instructions, or to retrieve the data that the constant represents or the instantiated object that the data points to, and this information is stored in the method area and Java heap shared by all threads.

2.4 Execution process of the Engine

Here’s a concrete example of how an execution engine might execute a piece of code in the execution unit:

public class Math {
    public static void main(String[] args) {
        int a=1;
        int b=2;
        int c=(a+b)*10; }}Copy the code

Take a look at the bytecode instruction for the main method:

0: iconST_1 constant 1 push 1: istore_1 move the top element of the stack to local variable 1 store 2: iconst_2 constant 2 push 3: istore_2 move the top element of the stack to local variable 2 store 4: ILOAD_1 local variable 1 push 5: Iload_2 local variable 2 to the stack 6: Iadd pops out of the top of the stack add two elements 7: bipush 10 10 to the stack 9: IMul top of the stack multiply two elements 10: ISTore_3 top of the stack move to the local variable 3 store 11: returnCopy the code

The following figure shows the components corresponding to the execution engine.

Before the method is executed, the POINTER stored in the PC register is the address of instruction 1, and the local variable area and operation stack have no data. From the first instruction to the fourth instruction, local variables A and B are assigned values respectively. Corresponding to the local variable area, 1 and 2 store constants 1 and 2 respectively, as shown in the figure below.

After the execution of the first four instructions, the PC register is currently pointing to the address of the next instruction, that is, the fifth instruction. At this time, the local variable area has saved two local variables (that is, the values of A and B), and there is still no value in the operation stack, because the two constants on the stack and then respectively off the stack.

Instructions 5 and 6 stack two local variables and add them, respectively.

1 is pushed first and then 2, and the top element of the stack is 2. The seventh instruction is to add the two elements on the top of the stack after they pop up, and the result is pushed again. At this time, the state of the whole component is shown in the figure below.

The current PC register executes at address 9, and the next operation multiplies the two operands on the current stack and pushes the result onto the stack.

The 10th instruction is to store the current top of the stack element in local variable 3.

After the execution of the 10th instruction, the element in the stack is removed from the stack, and the element out of the stack is stored in the local variable area 3, corresponding to the value of variable C. The last instruction is return, after which the parts of the current method are reclaimed by the JVM, all values in the local variable area are released, PC registers are destroyed, and the stack frames corresponding to the method in the Java stack disappear.

2.5 JVM method call stack

JVM method calls are divided into Java method calls and local method calls. The implementation of local method invocation varies from virtual machine to virtual machine, so this section focuses on Java method invocation.

As in the following code for a method call:

public class Math {
    public static void main(String[] args) {
        int a=1;
        int b=2;
        int c=math(a,b)/10;
    }

    public static int math(int a, int b){
        return (a+b)*10; }}Copy the code

The corresponding bytecode is as follows:

public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: invokestatic  #2 // Method math:(II)I
       9: bipush        10
      11: idiv
      12: istore_3
      13: return

  public static int math(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: iadd
       3: bipush        10
       5: imul
       6: ireturn
Copy the code

When the JVM executes the main method, it first stores the two constants 1 and 2 into local variable areas 1 and 2, respectively, and then calls the static main method. As you can see from the Math bytecode instructions, the two arguments to the Math method are also stored in the local variable areas 0 and 1 in the corresponding method stack frame. These two local variables are pushed separately, then added, multiplied by the constant 10, and finally returned. Here’s a look at how it works in the century’s executive operating parts.

The diagram above shows the state of the execution engine components when the JVM executes instruction 5. The PC register points to the address where the main method is next executed. When invokestatic is executed, the JVM creates a new stack frame for the Math method and stores the two parameters in the first two local variable fields of the Math stack frame. The PC register is zeroed out and points to the first instruction address of the Math stack frame. The state is shown in the figure.

When the invokestatic instruction is executed, a new stack frame is created, and the local variable area of the stack frame already contains two variables passed from the operation stack of the main method’s stack frame. When the Math method is executed, the stack frame corresponding to the Math method becomes the current active stack frame, and the PC register holds the address of the next instruction in the current stack frame, so it is 0.

The Math method adds variables A and B, multiplies them by 10, and returns the result to instruction 5.

The last instruction is iReturn. This instruction returns the top element of the current stack frame to the calling stack, and the stack frame is destroyed. The VALUE of the PC register is restored to the address of the next instruction on the calling stack.

When the return instruction is executed, the stack frame corresponding to the main method will also be destroyed. If there is no stack frame in the Java stack corresponding to the current thread, the Java stack will be destroyed by the JVM, and the entire JVM exits.

3. Summary

This article focuses on the architecture of the JVM, the execution engine of the JVM and the process of JVM instructions. In fact, the design of the JVM is very complicated, including how the JVM automatically optimizes the bytecode as it executes and recompiles it into local code, also known as JIT. This technology when we execute the test may have effect, if your application has not been fully “warm up”, so the results may be inaccurate, for example, the JVM will record a method to execute a program execution times, if the number of times to a threshold JIT will compile this method into native code.

At the end of the article, consider the question, why does recursion cause stack overflows?

This article is excerpted from Chapter 7 of An In-depth Look at Java Web Technology, and I’ve added some images to make it easier to understand. E-books can follow my public account, reply [ebook], you can get.

Recommended reading

This one is enough for learning reflexes