Deciphering Java Bytecode

JVM data types

Java is statically typed, which affects the design of bytecode instructions such that instructions expect themselves to operate on values ​​of a specific type. For example, there are several add instructions for adding two numbers: iadd, ladd, fadd, dadd. They expect operands of types to be int, long, float, and double, respectively. Most bytecodes have the property of having the same functionality in different forms, depending on the operand types.

JVM-defined data types include

1. 基本类型

- Numeric types: byte (1 byte), short (2 bytes), char (2 bytes), int (4 bytes), float (4 bytes), long (8 bytes) ), double (8 bytes)
- boolean type
- pointer type: instruction type

2. 引用类型

- Class
- Array
- Interface

Support for boolean types in bytecode is limited. For example, there is no structure that can directly manipulate boolean values. The substitution of boolean values ​​to int is done by the compiler, and is ultimately converted to an int structure.

stack-based architecture

The simplicity of the bytecode instruction set is largely due to Sun's design of a stack-based VM architecture rather than a register-based architecture. There are various processes that use JVM-based memory components, but basically only the JVM heap needs to be scrutinized for bytecode instructions:

PC register : For each running thread in a Java program, there is a PC register that holds the address of the currently executing instruction.

Stack : For each thread, a stack is allocated in which local variables, method parameters and return values ​​are stored. When the stack pointer moves down, new memory is allocated; if it moves up, that memory is freed.

Heap : Memory and storage objects (class instances and arrays) shared by all threads. Object reclamation is managed by the garbage collector.

Method area : For each loaded class, it stores the code for the method and a symbol table (eg references to fields or methods) and constant pool.

Bytecode Exploration

opcode illustrate
iconst_1 Put the integer variable 1 on the top of the operand stack
bipush 125 Convert 125 of type byte to type int and push it onto the stack
istore_1 Pop the top element of the stack and store it in the local variable with index 1
iload_1 Get the number in the local variable with index 1 and push it to the top of the stack
iadd Pop two elements from the top of the stack, add them, and push the result to the top of the stack

- When the int value is -1~5, the icont instruction is used

When the int value is 0~5, the JVM uses the icont_0, icont_1, icont_2, icont_3, icont_4, icont_5 instructions to push the constant onto the stack, and when the value is -1, the icont_m1 instruction is used to push the constant onto the stack.

  • When the int value is -128~127, the JVM uses the bipush instruction to push the constant onto the stack.
  • When the int value is -32768~32767, the JVM uses the sipush instruction to push the constant onto the stack.
  • When the int value is -2147483648~2147483647, the JVM uses the ldc instruction to push the constant onto the stack.

Take a chestnut :

public class Text{
    public static void main(String[] args){
        int a = 1;
        int b = 2;
        int c = a + b;
    }
}

print the compiled bytecode

javac Text.java  //首先进行编译
javap -v Text.class  //运行javap查看字节码

A lot of results are printed, we only look at the following parts

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V //1
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: istore_3
         8: return
         ······
  1. descriptor: The parameter of the method is an array of strings ([Ljava/lang/String;), and the return value is void (V).
  2. flags: Indicates that the method is public (ACC_PUBLIC) and static (ACC_STATIC).

Instruction execution description from address 0 to 8:

iconst_1: push the integer variable 1 to the top of the stack
image

istore_1: Pop the top element of the stack and store it in the local variable with index 1
image

iconst_2: push the integer variable 2 to the top of the stack
image

istore_2: pop the top element of the stack and store it in the local variable with index 2
image

iload_1: Take the number in the local variable with index 1 and push it to the top of the stack
image

iload_2: Get the top of the push stack in the local variable with index 2
image

iadd: pop the first two elements on the top of the stack and add, then push the result to the top of the stack
image

istore_3: pop the top element of the stack and store it in the local variable with index 3
image

return: return from this void method

It can be seen from the above that each step of the instruction has the precise execution of the JVM.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324858851&siteId=291194637