5. Method calls and return instructions

Method call instruction

Methods: Invokevirtual, InvokeInterface, Invokespecial, Invokestatic, invokedynamic

The following five instructions are used for method calls:

  • The Invokevirtual directive is used to invoke instance methods of an object and dispatch (virtual method dispatch) according to the actual type of the object, supporting polymorphism. This is also the most common method dispatch method in the Java language. (Rewriting method in polymorphic scene)
  • The InvokeInterface directive is used to invoke an interface method, which searches at run time for the interface method implemented by a particular object and finds an appropriate method to invoke.
  • The Invokespecial directive is used to invoke instance methods that require special processing, including instance initializer methods (constructors), private methods, and superclass methods. These methods are statically typed and are not distributed dynamically when called. (mainly methods that cannot be overridden)
  • The Invokestatic directive is used to invoke class methods (static methods) in named classes. This is statically bound.
  • Invokedynamic: invokes dynamically bound methods, a new instruction in JDK1.7. Used to dynamically resolve the method referenced by the call point qualifier at run time and execute that method. The dispatch logic for the previous four invocation instructions is hardwired into the Java virtual machine, whereas the dispatch logic for the InvokeDynamic instruction is determined by the bootstrapped method specified by the user.

Ex. :

invokespecial



invokestatic

// invokeStatic: static dispatch public void invoke2(){methodStatic(); } public static void methodStatic(){ }Copy the code

invokestatic



invokevirtual



Supplementary description of method call instructions

Method return instruction

Before the method call ends, a return is required. Method return directives are distinguished by the type of return value.

  • These include iReturn (used when the return value is Boolean, byte, CHAR, short, and int), lReturn, freturn, Dreturnareturn, and
  • There is also a return directive for methods declared as void, instance initializers, and class initializers for classes and interfaces.
The return type Return instructions
void return
Int (Boolean, byte, char, short) ireturn
long lreturn
float freturn
double dreturn
reference areturn

For example:

  • With the iReturn instruction, the top element of the operand stack of the current function is ejected and pushed into the operand stack of the caller (because the caller is concerned about the return value of the function), and all other elements in the operand stack of the current function are discarded.
  • If the synchronized method is currently returned, an implicit Monitorexit directive is also executed to exit the critical section.
  • Finally, the entire frame of the current method is discarded, the caller’s frame is restored, and control is transferred to the caller

6. Operand stack management instructions

The operand stack management instructions provided by the JVM can be used to manipulate the operand stack instructions directly, just as they would in a normal data structure.

Such directives include the following:

  • Pop one or two elements off the top of the stack and discard them :pop, pop2;
  • Duplicates one or two values from the top of the stack and pushes the duplicates or duplicates back to the top: dUP, DUP2, DUp_X1, DUp2_X1, DUP_x2, dup2_x2;
  • Swap the top two slots on the stack :swap. The Java virtual machine does not provide instructions to exchange two 64-bit long double values.
  • The instruction NOP is a very special instruction whose bytecode is 0x00. Like NOP in assembly language, it means to do nothing. This instruction can be used for debugging, placeholders, etc.

These instructions are generic and do not specify a data type for pushing or ejected from the stack.

These instructions are generic and do not specify a data type for pushing or ejected from the stack.

Description:

  • Instructions without _x copy and push data to the top of the stack. There are two instructions, dup and dup2. The coefficient of dUP represents the number of slots to copy.
    • The dUP command is used to copy the data of one Slot. For example, 1 int or 1 reference data
    • Instructions beginning with dup2 are used to copy data from two slots. For example, 1 long, or 2 ints, or 1 int+1
  • The _x instruction copies the top of the stack and inserts it somewhere below the top. There are four instructions, dup_x1, dup2_X1, dup_x2, dup2_x2. For copy insert instructions with _x, simply add the dUP of the instruction and the coefficient of x, and the result is the position to insert. so
    • Dup_x1 insert position :1+1=2, i.e., under the top 2 slots of stack
    • Insert dup_x2 :1+2=3, i.e., below the top 3 slots on the stack
    • Dup2_x1 insert position :2+1=3, that is, under the top three slots on the stack
    • Dup2_x2 :2+2=4, that is, under the top four slots on the stack
  • Pop: Removes the value of the Slot at the top of the stack. For example, a value of type short
  • Pop2: removes the two slots at the top of the stack. For example, 1 double, or 2 ints

case

7. Control transfer instruction

Conditional control is indispensable to program flow. In order to support conditional jump, virtual machine provides a large number of bytecode instructions, which can be roughly divided into 1) comparison instruction, 2) conditional jump instruction, 3) comparison conditional jump instruction, 4) multi-conditional branch jump instruction, 5) unconditional jump instruction, etc.

Compare instruction (this is an arithmetic instruction)

Comparison instructions:

  • The compare instruction compares the size of the two elements at the top of the stack and pushes the result onto the stack.
  • The comparison commands are DCMPG, DCMPL, FCMPG, FCMPL, and LCMP.

Similar to the instructions explained earlier, the first character d represents the ouble type,f represents float, and L represents long

  • For doublefloat and numbers of type, there are two versions of the comparison instruction due to aN. For float, there are FCMPG and FCMPL directives. The difference between them is that when a NaN value is encountered during a numeric comparison, the result is different.
  • Instruction DCMPL and DCMPG are similar, and their meanings can be inferred from their names, which will not be described here.
  • The LCMP instruction is for long integers. Since long integers have no NaN value, there is no need to prepare two sets of instructions.

For example: instructions FCMPG and FCMP1 pop up two operands from the stack, and compare them, according to the top element v2, stack top sequence 2 element V1, if V1 =v2, the pressing; If v1>v2, press 1; If v1 is less than v2, we push negative 1. The difference between the two instructions is that FCMPG presses 1 if a NaN value is encountered, while FCMPL presses -1.

Conditional jump instruction

Conditional jump instructions are usually used in conjunction with comparison instructions. When the conditional jump is still in progress, it is generally possible to use the comparison instruction to select the top element of the stack and then perform the conditional jump. Conditional jump instructions are: IFEq, IFLT, IFLE, IFNE, IFGT, IFGE, IFNULL, IFnonNULL. Each of these instructions receives a two-byte operand that calculates the jump position (a 16-bit signed integer as the offset for the current position). Their unified meaning is: pop the top element of the stack, test whether it meets a certain condition, if so, jump to a given location.

specify

instruction instructions
ifeq Equals Jumps when the top value of an int equals 0
ifne Not equals Jumps if the in value at the top of the stack does not equal 0
iflt Lower than Jumps when the value of the in type at the top of the stack is less than 0
ifle Lower or equals Jumps when the top in value of the stack is less than or equal to 0
ifgt Greater than Jumps if the array of int types at the top of the stack is greater than 0
ifge Greater or equals Jumps when the value of the in type at the top of the stack is greater than or equal to 0
ifnull If the value is null, it jumps
ifnonnull The value is not null

Note:

  1. Same as the previous operation rules
  • Boolean, byte, CHAR, short conditional branch comparisons are performed using int comparison instructions
  • For the conditional branch comparison operation of type 1ONG, FLDAT and double, the corresponding type comparison operation instruction will be executed first, which will return an integer value to the operand stack, and then execute the conditional branch comparison operation of type int to complete the branch jump
  1. The Java virtual machine provides the most abundant and powerful int-type conditional branch instructions, because all types of comparisons eventually become int-type comparisons.

Ex. :

Ifeq example



Ifnonnull example



Comprehensive comparison instruction examples

Compare conditional jump instructions

The comparison conditional jump instruction is similar to the combination of comparison instruction and conditional jump instruction, which combines the two steps of comparison and jump into one. The commands include IF_ICMPEq, IF_ICMPne, IF_ICMplt, IF_ICMPgt, IF_ICMple, IFICMPGE, and IF_ Acmpeq and if_ACmpne, where the instruction mnemonic is followed by “if_”, the instruction beginning with the character “” is for int integer operations (including ishort and byte), and the instruction beginning with the character” A “represents the comparison of object references.

specify

instruction instructions
if_icmpeq Compares the size of two int values at the top of the stack, jumps if the current value equals the latter
if_icmpne Compares the size of two int values at the top of the stack, jumps if the current value is not equal to the latter
if_icmplt Compares the size of two int values at the top of the stack
if_icmple Compares the size of two int values at the top of the stack, jumps if the value is less than or equal to the latter
if_icmpgt Compares the size of two ints at the top of the stack, jumps if the current value is greater than the latter
if_icmpge Compares the size of two int values at the top of the stack, jumps if the current value is greater than or equal to the latter
if_acmpeq Compares values of two reference types at the top of the stack, jumping if the results are equal
if_acmpne Compares values of two reference types at the top of the stack, jumping if the results are not equal

Each of these instructions takes a two-byte operand as an argument that calculates the jump location. At the same time, two elements need to be prepared at the top of the stack for comparison when executing the instruction. When the instruction completes, the top two elements of the stack are cleared and no data is added to the stack. If the default condition is true, the jump is executed; otherwise, the next statement continues.

Multi-conditional branch hops

The multi-conditional branch command is designed for switch – case statements, including tableswitch and lookupswitch.

Instruction names describe
tableswitch Used for switch conditional jump, case value continuous
lookupswitch Used for switch conditional jump, case value is not consecutive

Both are mnemonic implementations of switch statements. The difference is:

  • Tableswitch requires that multiple conditional branch values be consecutive. It stores only the start value, end value, and several forward offsets. You can locate the forward offset immediately by using the specified operand index.
  • The lookupswitch instruction stores discrete case-offset pairs internally. Each execution searches all case-offset pairs to find the matching case value and calculates the jump address according to the corresponding offset, which is inefficient.

The following figure shows the diagram of tableswitch. Because the case values of tableswitch are consecutive, you only need to record the lowest value, highest value, and offset offset of each item. You can directly locate the offset based on the specified index value by simple calculation.

The instruction 1ookupswitch deals with discrete case values, but for efficiency consideration, the case-offset pair is sorted according to the size of the case value. When the index is given, the case equal to the index needs to be searched and its offset is obtained. If no case can be found, the switch goes to default. Instruction 1ookupswitch is shown below.

Ex. :

Unconditional jump

At present, the main unconditional jump instruction is GOto. The instruction goto takes two bytes of operands, which together form a signed integer that specifies the offset of the instruction. The purpose of the instruction is to jump to the given offset.

If the instruction offset is too large, beyond the range of two-byte signed integers, the instruction goto_w can be used, which has the same effect as goto, but accepts four-byte operands and can represent a larger address range.

The JSR, jSR_w, and ret commands are also unconditional jumps, but they are mainly used in try finally statements and are gradually discarded by VMS. Therefore, they are not introduced here.

Instruction names describe
goto Unconditional jump
goto_w Unconditional jump (wide index)
jsr Jumps to the specified 16-bit offset position and pushes the address of the next JSR instruction to the top of the stack
jsr_w Jump to the specified 32-bit offer location and push the jSR_W instruction address to the top of the stack
ret Return to the instruction location given by the specified local variable (commonly used in conjunction with JSR and jSR_w)

Ex. :

Collocation of loop structure with GOTO

Think about:

  • WhileTest and forTest bytecode are the same; the difference is the scope of I
  • I++ will be executed at least once in doWhileTest
// Consider: How do the following two methods operate differently? public void whileTest(){ int i = 1; while(i <= 100){ i++; } public void forTest(){for(int I = 1; i <= 100; Public void doWhileTest(){int I = 1; do{ i++; }while(i <= 100); }Copy the code

8. Exception handling instructions

Throw exception instruction

  • (1) The athrow instruction

The operations (throws) that display an exception thrown in a Java program are implemented by the Athrow instruction. In addition to using throw statements to display thrown exceptions, the JVM specification specifies that many runtime exceptions are automatically thrown when other Java virtual machine instructions detect an exception condition. For example, in the integer arithmetic described earlier, the virtual machine throws an ArithmeticException in the IDIV or Ldiv instruction when the divisor is zero.

  • (2) Pay attention

Normally, the push and eject of the operand stack is done one instruction at a time. The only exception is when the Java virtual machine clears everything on the operand stack when an exception is thrown, and then pushes the exception instance onto the caller’s operand stack.

Exception and exception handling:

  • Procedure 1: Exception object generation process –> throw(manual/automatic) –> instruction: athrow
  • Process 2: Exception handling: grab model. Try-catch-finally –> Use the exception table

Exception handling and exception table

  1. Handle exceptions

In Java virtual machines, catch statements are handled not by bytecode instructions (JSR, RET instructions were used earlier), but by exception tables. If a method defines a try catch or try finally exception handler, an exception table is created. It contains information for each exception handler or finally block. The exception table holds each exception handling information. For example: – start position – end position – offset address of code processing recorded by the program counter – index of the caught exception class in the constant pool

When an exception is thrown, the JVM looks for a matching handle in the current method. If it doesn’t find one, the method is forced to terminate and the current stack frame is popped, and the exception is rethrown to the upper method called (in the calling method stack frame). If no suitable exception handling is found before all stack frames are ejected, the thread terminates. If this exception is thrown in the last non-daemon thread, it will cause the JVM itself to terminate, for example if the thread is a main thread.

Whenever an exception is thrown, if the exception handling ends up matching all exception types, the code continues to execute. In this case, if the method ends without throwing an exception, the finally block is still executed, and it jumps directly to the finally block to accomplish its goal before returning.

Ex. :



Example of try Finally code execution

9. Synchronize control commands

The Java virtual machine supports two synchronization structures: method-level synchronization and synchronization of a sequence of instructions within a method, both of which are supported using Monitor.

Method level synchronization

Method-level synchronization: it is implicit, that is, it is not controlled by bytecode instructions, and it is implemented in method calls and return operations. A virtual machine can tell whether a method is declared to be synchronized from the ACC_SYNCHRONIZED access flag in the method table structure of the method constant pool.

When a method is called, the calling instruction checks that the method’s AC_SYNCHRONIZED access flag is set.

  • If set, the thread of execution will first hold the synchronization lock and then execute the method. Finally, the synchronization lock is released when the method completes, either normally or abnormally.
  • During method execution, the executing thread holds the synchronous lock, and no other thread can acquire the same lock again.
  • If a synchronized method throws an exception during execution and cannot handle the exception inside the method, the lock held by the synchronized method is automatically released when the exception is thrown outside the synchronized method.

For example:

private int i= 0;
public synchronized void add(){
    i++;
}
Copy the code

Corresponding bytecode:

0 aload_0
1 dup
2 getfield #2 <com/atguigu/java1/SynchronizedTest.i>
5 iconst_1
6 iadd
7 putfield #2 <com/atguigu/java1/SynchronizedTest.i>
Copy the code

Note: This code is no different from regular non-sync code, it does not use Monitorenter and Monitorexit for sync zone control. This is because for synchronized methods, when the virtual machine determines that the method is a synchronized method by its access identifier, it automatically locks the method before calling it. When the synchronized method completes, the virtual machine releases the lock regardless of whether the method ends normally or an exception is thrown. Thus, for synchronous methods, the Monitorenter and Monitorexit directives are implicit and do not appear directly in the bytecode.

The synchronization of the specified instruction sequence within the

To synchronize a sequence of instructions: typically represented by a block of synchronized statements in Java. The JVM’s instruction set includes monitorenter and Monitorexit directives to support the semantics of the synchronized keyword.

When a thread enters a block of synchronized code, it uses the Monitorenter directive to request entry. If the monitor counter of the current object is 0, it is allowed to enter, if it is 1, it determines whether the thread holding the current monitor is itself, if so, it enters, otherwise it waits until the monitor counter of the object is 0 before it is allowed to enter the synchronized block.

When a thread exits a synchronized block, it needs to declare exit using MonitoreXit. In the Java Virtual machine, any object has a monitor associated with it to determine whether the object is locked. When the monitor is held, the object is locked.

The monitoRenter and MonitoreXit directives both execute by pressing an object at the top of the operand stack. The MonitoRenter and Monitorexit lock and release the object against the monitor.

The following figure shows how the monitor protects the critical section code from being accessed by multiple threads at the same time. Only after thread 4 leaves the critical section can threads 1, 2, and 3 enter.

Ex. :