The virtual machine stack

Background of the VM stack

Because of the cross-platform and CPU architecture differences, you can’t be register-based. Java’s instructions are stack based.

The advantage is that it is cross-platform, has a small instruction set, and is easy for the compiler to implement. The disadvantage is that it degrades performance and requires more instructions to implement the same function. This article describes virtual machine stacks

Vm Stack -Java Virtual Machine Stack

  • Java Virtual Machine Stack (Java Virtual Machine Stack) Java Virtual Machine Stack (Java Virtual Machine Stack)
  • Each thread creates a virtual machine Stack at creation time, which holds Stack frames that correspond to each Java method call. It is thread private
  • The life cycle is consistent with the thread
  • Functions: It is responsible for running Java programs. It holds local variables (8 basic data types, object reference addresses) of methods, partial results, and participates in method invocation and return.
  • Local variables: relative to member variables (or attributes), if they are basic data variables, they are created in the stack and destroyed as the stack is destroyed. Objects are generally created in the heap, and object handles on the stack are references to objects in the heap. Escape analysis can create objects in the stack, as described in a later section
  • Basic data variables: As opposed to reference type variables (class, array, interface), basic data variables are created on the stack and destroyed as the stack is destroyed.

Run-time data area structure diagram

The characteristics of the stack

  • Stacks are a fast and efficient way to allocate storage, second only to PC registers (program counters) in access speed.
  • There are only two direct JVM operations on the Java stack
    • Each method is executed with a push (push, push)
    • Exiting the stack after the execution is complete
  • There is no garbage collection problem with the stack

Stack and heap difference

  1. The stack
  • The stack is a unit of runtime, that is: the stack solves the running problem of the program, that is, how the program executes, or how the data is processed.
  • The stack space holds local variables of the basic data type, as well as references to objects that reference the data type
  • Each thread creates a virtual machine Stack at creation time, which holds Stack frames that correspond to each Java method call. It is thread private and has the same lifecycle as the thread.
  1. The heap
  • Heap is the unit of storage, namely: heap solves the problem of data storage, that is, how and where data is put.
  • Generally speaking, objects are primarily placed in the heap space, which is the larger part of the runtime data area, and it is thread public.

Relationship between JVM threads and operating system threads

A thread is a running unit in a program. The JVM allows multiple threads of an application to execute in parallel.

  • In HotSpotJVM, each thread maps directly to the operating system’s native thread.

    • When a Java thread is ready to execute, a local thread of the operating system is created at the same time. When the Java thread terminates, the local thread also reclaims I
  • The operating system is responsible for scheduling all threads to any available CPU. Once the local thread is successfully initialized, it calls the run() method in the Java thread.

  • The system space is used by the operating system, and the user space is used by the application. If the application needs to access the system space, it needs to make a system call and switch from the user mode to the kernel mode. After Java1.2. The JVM in Linux is implemented based on PThreads, and it can be directly said that Java threads are implemented in a 1:1 relationship depending on the operating system.

JVM daemon thread

  • Virtual machine threads: These thread operations are not performed until the JVM reaches a safe point. The reason these operations must occur in different threads is that they all require the JVM to reach a safe point so that the heap does not change. Execution types for this thread include “stop-the-world” garbage collection, thread stack collection, thread hang, and bias lock dump.
  • Periodic task threads: These threads are representations of periodic events (such as interrupts) that are used to schedule execution of periodic operations.
  • GC threads: These threads support different kinds of garbage collection in the JVM.
  • Compile thread: This thread compiles bytecode to native code at run time.
  • Signal scheduling thread: This thread receives signals and sends them to the JVM for processing by calling the appropriate method within it.

Processes are different from threads

  • Process: The minimum unit of resource allocation by the operating system is a process. Processes are isolated from each other. Each process has its own memory space and file descriptor

  • Thread: A thread exists in a process and is the smallest unit of scheduling execution in the operating system. In layman’s terms, threads do the work.

Threads are private and public

  • Each thread: independently includes program counter, stack, local stack.

  • Interthread sharing: heap, out-of-heap memory (permanent or meta space, code cache)

How stacks work

  • Each thread has its own Stack, and the data in the Stack is in the format of a Stack Frame
  • Each method being executed on this thread corresponds to its own stack frame
  • A stack frame is a block of memory, a data set that holds various data information during the execution of a method
  • There are only two operations that the JVM can do directly on the Java stack, namely push and exit of the stack frame, following the first-in, first-out/last-in, first-out sum principle.
  • In an active thread, there is only one active stack frame at a point in time. That is, only the stack Frame (top of the stack Frame) of the currently executing method is valid. This stack Frame is called the Current Frame, and the corresponding method is called the Current Frame.
  • All bytecode instructions run by the execution engine operate only on the current stack frame
  • If another method is called within the method, a new stack frame is created and placed at the top of the stack as the new current stack frame.
  • Stack frames contained in different threads are not allowed to reference each other, that is, it is not possible to reference another thread’s stack frame in another stack frame
  • If the current method calls another method, when the method returns, the current stack frame returns the result of the method to the previous stack frame. The virtual machine then dismisses the current stack frame, allowing the previous stack frame to become the current stack frame again
  • There are two ways for Java methods to return functions. One is to return normal functions, using the return instruction. The other is to throw an exception. Either way, the stack frame will be ejected.

public class StackFrameTest { public static void main(String[] args) { try { StackFrameTest test = new StackFrameTest();  test.method1(); } catch (Exception e) { e.printStackTrace(); } system.out.println ("main() ends normally "); } public void method1(){system.out.println ("method1() starts execution..." ); method2(); System.out.println(" Method1 () ends...") ); // System.out.println(10 / 0); // return ; } public int method2() {system.out.println (" Method2 () starts execution..."); ); int i = 10; int m = (int) method3(); System.out.println(" Method2 () is about to end...") ); return i + m; } public double method3() {system.out.println (" Method3 () starts..." ); Double j = 20.0; System.out.println(" Method3 () is about to end...") ); return j; }}Copy the code

There are two ways to end a method:

  • Normal end, denoted by return
  • Method execution ends with an exception thrown for an unhandled exception

The output of the push and exit results is as follows:

Method1 () starts execution... Method2 () starts execution... Method3 () starts executing... Method3 () is coming to an end... Method2 () is coming to an end... Method1 () execution ends... Main () ends normallyCopy the code

The stack anomalies

The Java Virtual Machine specification allows the size of the Java stack to be dynamic or fixed

  • Fixed size: StackOverflowError is thrown if a thread requests a stack depth greater than the maximum allowed by the virtual machine.
  • Dynamic expansion: An OutOfMemoryError is thrown if the VM cannot obtain sufficient memory space when extending the stack.

Stack StackOverFlowError

Methods are called recursively and StackOverFlowError occurs when the stack depth is exceeded.

/** * StackOverflowError ** Default: count: 11420 * Set stack size: -xss256K: count: 2465 */ public class StackErrorTest { private static int count = 1; public static void methodA() { count++; System.out.println(count); methodA(); } public static void main(String[] args) { methodA(); }}Copy the code

Stack OutOfMemoryError

If the test is not limited to a single thread, it is possible to generate a memory overflow exception by continuously creating threads, as shown in the following code. However, there is no correlation between the size of the stack and the overflow exception, or precisely, in this case, the larger the stack memory allocated for each thread, the more likely the overflow exception is.

If you use the virtual machine default parameter, a stack depth of 1000 to 2000 is perfectly fine in most cases (since each method is not pushed to the same frame size), and should be perfectly adequate for normal method calls (including recursion). However, if the memory overflow is caused by the establishment of multiple threads, you can only reduce the maximum heap and reduce the stack capacity in exchange for more threads without reducing the number of threads or replacing the 64-bit virtual machine

Code execution carries a significant risk of causing the operating system to fake death.

public class JavaVMStackOOM { private void dontStop() { while (true) { } } public void stackLeakByThread() { while (true) { new Thread(new Runnable() { @Override public void run() { dontStop(); } }).start(); } } public static void main(String[] args) { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); }}Copy the code

Set the stack size

  • -xss option to set the maximum stack space of the thread. The stack size directly determines the maximum reachable depth of the function call. (- Xss256k)
  • JDK8 default stack size:

Sets the thread stack size (in bytes). Append the letter k or K to indicate KB, m or M to indicate MB, or g or G to indicate GB. The default value depends on the platform:

  • Linux/x64 (64-bit): 1024 KB

  • macOS (64-bit): 1024 KB

  • Windows: The default value depends on virtual memory

    The following examples set the thread stack size to 1024 KB in different units:

    -Xss1m
    -Xss1024k
    -Xss1048576
    Copy the code

    This option is similar to -XX:ThreadStackSize.

Xss is – XX: ThreadStackSizeOpenJDK and Oracle JDK alias.

Although they parse parameters in different ways:

-xss accepts numbers with K,M, or G;

-xx :ThreadStackSize= Requires an integer (no suffix) – stack size in kilobytes.

Jdk8 virtual machine parameter check url: docs.oracle.com/en/java/jav…

Internal structure of the VIRTUAL machine stack

  • Local Variables
  • Operand Stack (or expression Stack)
  • Dynamic Linking (or a method reference to a pool of runtime constants)
  • Method Return address (or the definition of whether the method exits normally or unexpectedly)
  • Additional information

Local Variable table -Local Variable

  • A local variable table is also called an array of local variables or a local variable table

  • An array of numbers used to store method parameters and local variables defined within the method body. These data types include the basic data types (Boolean, byte, char, short, int, float), object reference (Float), And the returnAddress type. The Reference type represents a reference to an object instance

  • Since the local variable table is built on the stack of threads and is thread private data, there is no data security issue

  • The required size of the local variables table is determined at compile time and is stored in the Maximum Local Variables data item in the Code property of the method. The size of the local variable table does not change during the run of the method.

  • The number of nested method calls is determined by the size of the stack. In general, the larger the stack, the more nested methods are called. For a function, the more parameters and local variables it has, the larger the local variable table, and the larger the stack frame, to accommodate the increased amount of information that the method calls need to pass. In turn, function calls take up more stack space, resulting in fewer nested calls.

  • Variables in the local variable table are valid only for the current method call. During method execution, the VIRTUAL machine passes the parameter values to the parameter variable list by using the local variable table. When the method call ends, the local variable table is destroyed along with the method stack frame.

Bytecode viewing

public class LocalVariablesTest { private int count = 0; public static void main(String[] args) { LocalVariablesTest test = new LocalVariablesTest(); int num = 10; test.test1(); } // exercise: public static void testStatic() {LocalVariablesTest test = new LocalVariablesTest(); Date date = new Date(); int count = 10; System.out.println(count); // The following row will report an error: this variable does not exist in the local variable table of the current method! Static is loaded with the class. This is the instance object. // system.out.println (this.count); } public LocalVariablesTest() {this.count = 1; } public void test1() { Date date = new Date(); String name1 = "atguigu.com"; test2(date, name1); System.out.println(date + name1); } public String test2(Date dateP, String name2) { dateP = null; name2 = "songhongkang"; Double weight = 130.5; Char gender = 'male '; return dateP + name2; } public void test3() { this.count++; } public void test4() { int a = 0; { int b = 0; b = a + 1; } // int c = a + 1; } / * variables classification: according to the data type points: (1) basic data types (2) reference data type According to the statement in the class location points: (1) member variables: before use, had been in the default initialization assignment class variables: linking the prepare phase: default assignment for class variables - > initial stage: Explicit assignment to class variables is a static code block assignment to instance variables: as an object is created, instance variable space is allocated in the heap and default assignment is performed. (2) Local variables: explicit assignment must be performed before use! */ public void test5Temp() {int num; //System.out.println(num); // Error message: num is not initialized}}Copy the code

What is a slot

A local variable table is a set of variable value stores for method parameters and local variables defined within methods. When the Java program is compiled as a Class file, the max_locals item in the Code property of the method determines the maximum size of the local variables that the method needs to allocate.

The capacity of a local variable table is the minimum unit of variable slots. The VM specification does not specify the memory space that a slot should occupy. Each slot should be able to hold data of type Boolean, byte, char, short, int, float, Reference, or returnAddress. This description is a little different from stating that each slot takes up 32 bits of memory, and allows the length of the slot to vary depending on the processor, operating system, or virtual machine. However, even if a 64-bit virtual machine uses a 64-bit length of memory to implement a slot, the virtual machine still uses alignment and padding to make the slot look the same in appearance as in a 32-bit virtual machine.

A slot can store a data type of less than 32 bits. In Java, there are eight data types that occupy less than 32 bits: Boolean, byte, char, short, int, float, refcrcnce, and retumAddress

Referencc is a reference to an object. The VIRTUAL machine specification does not specify its length, nor does it specify how the reference should be structured, but in general, the virtual machine implementation should at least be able to directly or indirectly find the starting address index of the object in the Java heap and the object type data in the method area from this reference. Whereas: etumAddress is for the bytecode instructions JSR, JSR_W, and RET and points to the address of a bytecode instruction.

For 64-bit data types, the virtual machine allocates two consecutive slot Spaces in a high-order fashion. The only explicit 64-bit data types in the Java language are long and double (reference can be either 32-bit or 64-bit). It is worth noting that the split between long and double is similar to the split between one long and double read and write into two 32-bit reads in the “Nonatomic convention for Long and Double,” which can be compared when reading the Java memory model. However, since local variables are built on the thread’s stack and are thread-private data, reading and writing to two consecutive slots does not raise data security concerns whether or not they are atomic operations.

The vm uses the local variable table by indexing. The index value ranges from 0 to the maximum number of slots in the local variable table. In the case of a 32-bit data type variable, the index n indicates that the NTH slot is used, and in the case of a 64-bit data type, the NTH and NTH + L slots are used. When a method is executed, the virtual machine uses the local variable table to pass the parameter values to the parameter variable list. For instance methods (non-static methods), then the slot at the 0th index in the local variable table is used by default to pass a reference to the instance of the object to which the method belongs. This implied parameter can be accessed in the method by the keyword “this”. The rest of the parameters are arranged in the order of the parameter table and occupy the local variable slots starting from 1. After the parameter table is allocated, the rest of the slots are allocated according to the variable order and scope defined inside the method body. The slots in the local variable table are reusable. The scope of variables defined in the method body does not necessarily cover the entire method body. If the value of the current bytecode PC counter is out of the scope of a variable, then the corresponding slot of the variable can be assigned to another variable. Not only is this designed to save stack space, but in some cases the reuse of slots can directly affect the garbage collection behavior of the system.

Slot Structure and features of a slot

  • The storage of parameter values always starts at index0 of the local variable array and ends at the index of array length -1
  • Local variable table. The most basic storage unit is the Slot.
  • The local variable table contains all kinds of basic data types (8 kinds) known at compile time, variables of reference type and returnAddress type.
  • In the local variable table, 32-bit or less types occupy only one slot (including the returnAddress type), and 64-bit types (long and double) occupy two slots.
    • Byte, short, char, float are converted to int before storage, Boolean is also converted to int, 0 means false, non-0 means true;
    • Long and double occupy two slots.
  • The JVM assigns an access index to each slot in the local variable table, which is used to successfully access the local variable value specified in the local variable table
  • When an instance method is called, its method parameters and local variables defined within the method body are copied to each slot in the local variable table in the order in which they are declared
  • If you need to access the local variable value of a 64-bit in the local variable table, you only need to use the previous index. (e.g., accessing variables of type long or double)
  • If the current frame was created by a constructor or instance method, then the object reference to this will be stored in a slot with index 0, and the rest of the parameters will be in the order of the parameter list.
  • Static methods cannot refer to this because this does not exist in the local variable table of the stack frame corresponding to the static method
  • The number of slots remains the same, and the capacity of the maximum local variable table is known at compile time.

Reuse of slots

The slots in the local variable table in the stack frame can be reused. If a local variable goes beyond its scope, new local variables declared after its scope are likely to reuse the slots of expired local variables, thus saving resources.

Bytecode determines the scope of local variables

For example: local variable a start PC: 2 Length: 7 Start PC=2= 8 lines of source = system.out.println (a); 2+7=9=int b = 0; It’s all valid

Operand Stack (or expression Stack)-Operand Stack

  • Each individual Stack frame contains a last-in-first-out Stack of operands, also known as the Expression Stack, In addition to the local variable table.

  • Operand stack. During the execution of a method, data is written or extracted from the stack according to bytecode instructions, i.e., push/pop.

    • Some bytecode instructions push the value onto the operand stack, and other bytecode instructions take the operand off the stack. Use them and push the results on the stack.
    • For example, perform operations such as copying, swapping, and summing
  • If the called method has a return value, its return value is pushed into the operand stack of the current stack frame, updating the PC register with the next bytecode instruction to be executed.

  • The data types of elements in the operand stack must strictly match the sequence of bytecode instructions, which is verified by the compiler during the compiler and again during the data flow analysis phase of the class validation phase of the class loading process.

  • In addition, we say that the Java Virtual Machine interpretation engine is a stack-based execution engine, where the stack refers to the operand stack.

  • Operand stack, mainly used to save the intermediate results of the calculation process, and as a temporary storage space of variables in the calculation process.

  • The operand stack is a workspace of the JVM execution engine. When a method is first executed, a new stack frame is created. The method’s operand stack is empty.

  • Each operand stack has an explicit stack depth to store the value. The maximum depth required is defined at compile time and is stored in the method’s Code property as the value max_stack.

  • Any element in the stack can be of any Java data type.

    • 32-bit types occupy one stack unit depth
    • The 64-bit type occupies two stack unit depths
  • The operand stack is not accessed by index, but only through standard push and pop operations to complete data access.

When a method just starts, its operand stack is empty, as the method of execution and bytecode instruction execution, from local variables or object instances in the field of reproduction constant or variable written to the operand stack, again with the calculation of the elements in the stack to the local variables or method is returned to the caller, that is out of/into the stack operation. The execution of a complete method often involves multiple such exit/push procedures.

Difference between vm stack and operand stack

  • The virtual machine stack is composed of stack frames, and each stack frame corresponds to a method call.
  • The operand stack is the part of the stack frame that is responsible for push/pop instructions within a stack frame. Operations such as summing two numbers within a method.

Bytecode instruction analysis

In the JVM, there are four types of bytecode instructions that are pushed according to the value range:

  • Values -1 to 5 Use the ICONST instruction.

  • Values -128~127 use bipush instruction.

  • Values -32768~32767 Sipush instruction is adopted.

  • Value -2147483648 to 2147483647 The LDC command is used.

More access to bytecode instruction referral url: docs.oracle.com/javase/spec…

Public void testAddOperation() {//byte, short, char, Boolean: save byte I = 15; int j = 8; int k = i + j; // int m = 800; }Copy the code

Use the JclassLib plug-in or the decompiled class file (javap -v xxx.class) to get the decompiled class file.

// The first instruction is: 2 istore_1 // Store the int value from the operand stack to the second slot in the local variable table (0 is this) 3 bipush 8 // Push the operand stack 8 5 istore_2 // Then store the int value from the operand stack to the local variable table Iload_2 // Load the local variable and push the value of the second slot into the operand stack 7 iload_2 // load the local variable and push the value of the third slot into the operand stack 8 iadd // Add the two numbers in the operand stack and then push the stack 9 istore_3 // Then store the int value from the stack of operands to the fourth slot in the local variable table (that is, the value of k)Copy the code

Note:

  • We know from the local variable table that the first slot of a non-static method is this. So istore_0 = this
  • The PC register (program counter) pointer changes accordingly

Top stack caching

  • As mentioned earlier, stack-based virtual machines use zero-address instructions that are more compact, but more push and exit instructions are required to complete an operation, which in turn means more instruction dispatches and memory reads/writes.

  • Because operands are stored in memory, frequent memory read/write operations inevitably affect execution speed. To solve this problem, the designers of HotSpot JVM proposed ToS (top-of-stack Cashing) technology, which cache all Stack items in registers of the physical CPU to reduce the number of reads/writes to memory and improve the efficiency of the execution engine.

Dynamic Linking (or a method reference to the runtime constant pool)-Dynamic Linking

  • Inside each stack frame is a reference to the method in the runtime constant pool to which the stack frame belongs. This reference is included so that code that supports the current method can be dynamically linked. For example, the Invokedynamic instruction

  • When Java source files are compiled into bytecode files, all variable and method references are held in the class file’s constant pool as Symbolic references (which have not yet been resolved (link phase – parse phase) into direct references to the run-time constant pool).

For example, when a method calls another method, it is represented by symbolic references to the method in the constant pool, and dynamic linking is used to convert these symbolic references into direct references to the calling method. The transformation process is in the: link phase – parse phase

Static link and dynamic link difference

In the JVM, converting a symbolic reference to a direct reference to the calling method is related to the method’s binding mechanism.

  • Static links:

When a bytecode file is loaded into the JVM, if the target method being called is known at compile time and remains unchanged at run time. The process of converting a symbolic reference to a calling method to a direct reference in this case is called static linking.

  • Dynamic links:

If the method to be called cannot be identified at compile time, that is, the symbolic reference to the calling method can be converted to a direct reference only at runtime. This reference conversion process is called dynamic linking because of its dynamic nature.

Early binding is different from late binding

The Binding mechanisms for static and dynamic linking methods are Early Binding and Late Binding. Binding is the process of replacing a symbolic reference to a field, method, or class with a direct reference, which occurs only once.

  • Early binding

Early binding is invoked if the target method at compile time, and the run-time remains the same, this method can be bound with subordinate type, as a result, due to clearly define the target method is called what – a, thus can use static link way converting symbols refer to reference directly.

  • Late binding

If the method to be called cannot be determined at compile time, only the relevant method can be bound at run time according to the actual type. This is called late binding.

The difference between virtual and non-virtual methods

  • background

With the advent of high level languages, there are more and more object-oriented programming languages like Java. Although there are definite differences in syntax style, they all have one common feature in common: they all support object-oriented features such as encapsulation, inheritance, and polymorphism. Since this class of programming languages is polymorphic, it is natural to have both early binding and late binding.

Any common method in Java actually has the characteristics of a virtual function, which is equivalent to a virtual function in the C++ language (where it is explicitly defined using the key virtual). If you don’t want a method to have the characteristics of a virtual function in your Java program, you can use the keyword final to mark the method.

  1. Non-virtual methods:
  • If the specific version of the call is determined at compile time, this version is immutable at run time. Such methods are called non-virtual methods.
  • Static methods, private methods, final methods, instance constructors, and superclass methods are all non-virtual methods.
  1. Other methods are called virtual methods.

The virtual machine provides the following method invocation instructions:

  • Normal call instruction:
  1. Invokestatic: Calls static methods, and the parse phase determines the unique method version
  2. Invokespecial: Calls init methods (constructors), private, and superclass methods. The resolution phase determines the unique method version
  3. Invokevirtual: calls all virtual methods
  4. Invokeinterface: Invokes interface methods
  • Dynamic call instruction:
  1. Invokedynamic: Dynamically parse out the method that needs to be called and execute it

The first four instructions are fixed within the virtual machine, and the invocation of the method is performed without human intervention, whereas the Invokedynamic instruction supports user determination of the method version. Methods called by the InvokeStatic and Invokespecial directives are called non-virtual methods, and the rest (except for final modifications) are called virtual methods.

Invokedynamic instruction

The JVM bytecode instruction set was relatively stable until Java7, when an invokedynamic instruction was added as an improvement to Java’s “dynamically typed language” support.

However, there is no direct method to generate invokedynamic instructions in Java7. You need to use the low-level bytecode tool ASM to generate invokedynamic instructions. Until the advent of Java8 Lambda expressions, the generation of Invokedynamic instructions, there was no direct generation in Java.

The nature of the dynamic language type support added in Java7 is a change to the Java Virtual Machine specification, rather than a change to the Java language rules, which is relatively complex. The most immediate benefit of increasing method calls in the virtual machine is the compiler of the dynamic language running on the Java platform.

Static language and dynamic language difference

The difference between a dynamically typed language and a statically typed language is whether the type is checked at compile time or at run time.

To put it more bluntly, statically typed languages judge the type information of variables themselves; Dynamic typing language is to judge the type information of the value of variables, variables do not have type information, variable value has type information (that is, according to the value to determine the type of variables), which is an important feature of dynamic languages.

Similar strongly typed languages (Java) and weakly typed languages (JS)

The nature of rewriting

The nature of method overrides in the Java language:

  1. Finds the actual type of object executed by the first element of the operand stack entry, denoted as C.
  2. If at the end of the process; If no type C found are in conformity with description in line with the simple name of constant access method of calibration, if through the direct reference is returned this method, to find, it returns the Java. Lang. IllegalAccessError anomalies.
  3. Otherwise, step 2 searches and validates each parent class of C from the bottom up according to their inheritance.
  4. If didn’t find the right way, it throws the Java. Lang. AbstractMethodError anomalies.

IllegalAccessError is introduced:

A program is trying to access or modify a property or call a method that you have no access to. Normally, this will cause a compiler exception. If this error occurs at runtime, an incompatible change has been made to a class.

To use polymorphism in subclass objects:

  • Class
  • Overwriting methods

Virtual method table

In object-oriented programming, dynamic dispatch is frequently used. If the process of dynamic dispatch has to search for the appropriate target in the method metadata of the class again, the execution efficiency may be affected. Therefore, to improve performance, the JVM creates a virtual method table in the method area of the class (non-virtual methods do not appear in the table). Use index tables instead of lookups.

  • Each class has a virtual method table that holds the actual entry to each method.

So when is the virtual method table created?

  • The virtual method table is created and initialized during the linking phase of class loading. After the class variables are initialized, the JVM initializes the method table for that class as well.

Virtual method examples are introduced

** / interface Friendly {void sayHello(); void sayGoodbye(); } class Dog { public void sayHello() { } @Override public String toString() { return "Dog"; } } class Cat implements Friendly { public void eat() { } @Override public void sayHello() { } @Override public void sayGoodbye() { } @Override protected void finalize() { } @Override public String toString() { return "Cat"; } } class CockerSpaniel extends Dog implements Friendly { @Override public void sayHello() { super.sayHello(); } @Override public void sayGoodbye() { } }Copy the code

Virtual method table of class Cat

Dog class virtual method table

Virtual method table of CockerSpanielg class

Conclusion: The establishment of virtual method table is to improve the efficiency of program execution. By creating a virtual method table. Causes a direct reference to the overridden method to point to the address of the overridden method in the subclass. For example, if a method line is not found in a subclass, then the JVM will directly go to the parent class to find the method, without traversing the parent class again.

Method Return address (or the definition of a method that exits normally or unexpectedly) -return Adress

  • Stores the value of the PC register from which the method was called.

  • There are two ways to end a method:

      1. Normal execution complete
      1. An unhandled exception occurred and exits abnormally
  • Either way, after a method exits, it returns to where the method was called. When a method exits normally, the value of the caller’s PC counter is used as the return address, which is the address of the next instruction to the instruction that called the method. If an exception exits, the return address is determined by the exception table, which is generally not stored in the stack frame.

Normal completion of exit

When the execution engine encounters a bytecode instruction (return) returned by any method, the return value will be passed to the upper level method caller, referred to as normal completion exit;

  • Which return instruction a method needs to use after the normal call is complete depends on the actual data type of the method’s return value.

  • In the bytecode instruction, the return instruction contains

    • Ireturn: used when the return value is a Boolean,byte,char,short, or int
    • Lreturn: returns a value of long
    • Freturn: The return value is float
    • Dreturn: The return value is double
    • Areturn: The return value is the reference class
    • Return: for methods declared as void, instance initializers, class initializers, and interface initializers.

Exception completion exit

An Exception is encountered during the execution of the method, and this Exception is not handled in the method, that is, as long as there is no matching Exception handler in the Exception table of the method, will cause the method to exit. Abnormal completion exit.

  • When an exception is thrown during the execution of a method, the exception handling is stored in an exception handling table, so that when an exception occurs, the code that handles the exception can be found.

In essence, method exit is the process of the current stack frame out of the stack. At this point, you need to restore the local variable table of the upper method, the operand stack, push the return value into the operand stack of the caller stack frame, set the PC register value, and so on, so that the caller method can continue executing. The difference between a normal completion exit and an exception completion exit is that an exception completion exit does not generate any return value for its upper caller.

Abnormal structure diagram

  • Error: An exception that should not have been caught. The program cannot be recovered and either the thread or the virtual machine needs to be terminated.
  • Exceptions: Exceptions that may need to be caught and handled.
  • RuntimeException: RuntimeException, non-checked exception (Error is also non-checked exception)
  • Other Exception subclasses: check exceptions that require explicit catching in the code. Or methodthrowstag
  1. How does the JVM catch exceptions
  • In compiled bytecode, each method is accompanied by an exception table.
  • Each entry in the exception table represents an exception handler
  • When an exception is triggered, the JVM iterates through the exception table, comparing whether the index value of the bytecode that triggered the exception is within the range of the exception handler’s FROM to to pointer.
  • After the scope matches, the exception type is compared to the type in the exception handler.
  • When the type matches, it jumps to the bytecode to which the target pointer points (the start of the catch block).
  • If no exception handler is matched, the Java stack frame for the current method is popped up and the above operation is repeated for the caller.
  1. Exception handling table
from to target type
0 3 6 IOException
0 3 11 Exception
  1. What is an exception handler? What are its components?
  • An exception handler consists of a FROM pointer, a to pointer, a target pointer, and the type of exception caught (type).
  • The value of these Pointers is the Bytecode index (BCI), which is directly used to locate the bytecode.
  • The from and to Pointers, identifying the returns monitored by the exception handler
  • Target pointer to the starting position of the exception handler. For example, the starting position of a catch block
  • Type: Type of the Exception caught, such as Exception
  1. What happens if no exception handler is matched in the exception table of the method?
  • The Java stack frame for the current method pops up
  • The process of repeating the exception match on the caller.
  • In the worst case, the JVM needs to compile an exception table for all methods on the current thread’s Java stack
  1. How is the finally block implemented?
  • The finally block is processed during compilation
  • The current version of the Java compiler copies the contents of the finally block and places them in the exits of all normal and exception execution paths.
  1. The finally block of code

    Public class Test {private int tryBlock; private int catchBlock; private int finallyBlock; private int methodExit; public void test(){ try { tryBlock = 0; }catch (Exception e){ catchBlock = 1; }finally { finallyBlock = 2; } methodExit = 3; }}Copy the code

To view the bytecode instruction: javap -v test.class

         0: aload_0
         1: iconst_0
         2: putfield      #2                  // Field tryBlock:I
         5: aload_0
         6: iconst_2
         7: putfield      #3                  // Field finallyBlock:I
        10: goto          35
        13: astore_1
        14: aload_0
        15: iconst_1
        16: putfield      #5                  // Field catchBlock:I
        19: aload_0
        20: iconst_2
        21: putfield      #3                  // Field finallyBlock:I
        24: goto          35
        27: astore_2
        28: aload_0
        29: iconst_2
        30: putfield      #3                  // Field finallyBlock:I
        33: aload_2
        34: athrow
        35: aload_0
        36: iconst_3
        37: putfield      #6                  // Field methodExit:I
        40: return
      Exception table:
         from    to  target type
             0     5    13   Class java/lang/Exception
             0     5    27   any
            13    19    27   any
Copy the code

There are three finally blocks

  • The first copy of the finally block, after the try code. An exception occurs in the try block and jumps to the catch block. If the catch fails to catch, it jumps to the last finally block.
  • The second copy of the finally block, after the catch code. An exception in the catch block will jump to the last finally block.
  • The last block of finally code, which is in the exception execution path, will throw the any exception. The finally block within the try and catch blocks also throws an any exception if an exception occurs.
  • Javap uses any to refer to all kinds of exceptions.

View the JClasslib plug-in

  • Start the PC = the from

  • PC = to end

  • Jump PC = target

  • Capture type =type

  • CatchBlock = 1; catchBlock = 1;

  • If the form 0 to 5 is not an Exception, jump to target 27 (finallyBlock = 2;)

  • .

Additional information

Some additional information related to the Java virtual Machine implementation is also allowed in the stack frame. For example, information that provides support for program debugging. In general, most blog introductions will only have the above four structures. I won’t elaborate on the additional information here.

Stack tuning ideas

  1. The problem
  • What about stack overflow? (StackOverflowError)

If recursion exceeds stack depth, set stack size by -xss; If the stack memory is allocated dynamically, the 00M problem will still exist if the stack memory is exceeded.

  • Can you adjust the stack size to ensure that there is no overflow?

No, can only delay the stack overflow time, can not fundamentally solve the problem, should optimize the code logical order.

  • Is it better to allocate more stack memory?

No! The larger the memory allocation stack, the larger the memory for each thread, and the smaller the number of threads for a given VM resource. The overall vm concurrency decreases, and too much allocated memory is wasted.

  • Does garbage collection involve the virtual machine stack?

Won’t! The virtual machine stack is thread private, and with the stack frame out of the stack, memory is waiting to be reclaimed.

  • Are local variables defined in methods thread-safe?

It is not necessarily safe on a case-by-case basis, except that some object reference escapes (escape analysis) can cause thread insecurity in local variables.

  1. tuning

The part of the stack frame most closely related to performance tuning is the local variable table. When a method is executed, the virtual machine uses a local variable table to pass the method

Variables in the local variable table are also important garbage collection root nodes, as long as the objects referenced directly or indirectly in the local variable table are not reclaimed

The local variable table is thread private and can be reclaimed at the end of the run. Make appropriate trade-offs for ease of writing and readability of the code.

Gleanings – variable classification

  1. By data type:
  • Basic data type

  • Reference data type

  1. According to the position declared in the class:
  • Member variables: All have undergone default initialization assignment before being used

Class variable: default assignment of class variable in the preparation phase of class linking — > Explicit assignment of class variable in the initialization phase, i.e. static code block assignment;

Not static: Instance variables: As the object is created, the instance variable space is allocated in the heap and is assigned by default

  • Local variables: must be explicitly assigned before they can be used! Otherwise, the compilation fails