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:
- 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
- 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
- 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. :