Java虚拟机-字节码指令
1 字节码指令
Java字节码指令的执行离不开操作数栈,局部变量表,和常量池。
2 常量池
对于字节码指定来说,常量池中的常量可以用来来描述一个类字段,类,接口,方法的。也可以用来表示一个基本类型数据,如字符串,数字等。当我们执行一个指令的时候通常会带上一个或多个常量常量作为操作数,用来表示指定所作用的目标。
案例
//ldc指令用来将常量池中指定的常量放入操作数栈中,这里指定的常量是#2
ldc #2
/**
*invokestatic指令用来调用类中静态方法。如果调用的静态方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈,静态方法执行完毕会将返回值推到操作数栈中。静态方法用常量池中常量来表示,该常量类型是一个方法的符号引用。
**/
invokestatic #3

3 操作数栈
每一个Java字节码指令的执行和操作数栈密不可分。操作栈是用来存储指令计算中参数和结果数据结构。在指令执行执行前我们会将要操作的数据放入栈中。执行中会将操作栈中的数据会取出计算,并将指令执行结果写入栈中。
3-1 静态变量初始化
如下指定完成了静态变量staticField初始化
0: ldc #10 // String 静态变量
2: putstatic #11 // Field staticField:Ljava/lang/String;
//对应Java源文件代码
public static String staticField = "静态变量";
-
1 ldc:ldc指令用来将常量池中指定的常量放入操作数栈中,这里指定的常量是#10,常量类型是String,其值#41指向一个类型为utf-8的常量,对应值为"静态变量"
-
2 putstatic:putstatic指令用来将操作数栈顶的元素(1步骤入栈元素)赋值给指定静态变量,并将栈顶元素从操作数栈中出栈。这里指定静态变量用常量池中第#11常量来表示,该常量类型是一个字段的符号引用。这里用来对类staticField静态变量初始化。
3-2 成员变量的初始化
成员变量field的初始化
如下指令用来将成员变量field的初始化。
4: aload_0
5: ldc #2 // String ==普通变量==
7: putfield #3 // Field field:Ljava/lang/String;
//对应Java源文件代码
public String field = "普通变量";
-
1 aload_0:将局部变量表中第一个局部变量的引用推到栈顶。在Java虚拟机中对于非静态方法调用,会将当前对象引用放入到局部变量表中第一个局部变量位置。【调用方法指令前,需要将调用方法的对象加入到操作数栈中。此步骤是调用构造函数的一个中间步骤】
-
2 ldc:将常量池中#2常量引用推入操作数栈。该常量是一个String类型的常量,其值#32指向utf-8字面量对应为字符串"==普通变量=="。
-
3 putfield:putfield指令用来将操作数栈顶的元素(2步骤入栈元素)赋值给指定成员变量,并从操作数栈中出栈。同时将新的栈顶元素(1步骤入栈元素)作为赋值成员变量所在的对象,并从操作数栈中出栈。这里指定成员变量用常量池中第#2常量来表示,该常量类型是一个字段的符号引用。这里获取的是类对象中field成员变量。
3-3 静态方法的调用
3: aload_1
4: invokestatic #3 // Method greeting:(Ljava/lang/String;)V
//对应Java源码
greeting(name);
-
1 aload_1:aload_1指令用来将局部变量表下标为1的元素的值推到操作数栈栈顶【这里是为了给下一步做参数】
-
2 invokestatic指令用来调用类中静态方法。如果调用的静态方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈,静态方法执行完毕会将返回值推到操作数栈中。静态方法用常量池中常量来表示,该常量类型是一个方法的符号引用。这里含义是调用类中静态方法greeting。greeting有一个参数,此时会将栈顶的元素(1步骤入栈元素)作为参数,其值指向指向常量池中类型为UTF-8的常量,值为“Louis”,并从操作数中出栈。由于println方法不存在返回值,因而不存在入栈操作。
3-4 打印成员变量值
如下指令用来执行代码块中第一行代码:打印成员变量field。
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: getfield #3 // Field field:Ljava/lang/String;
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
//对应Java源文件代码
{
System.out.println( field );
...
}
-
1 getstatic:getstatic指令用来表示获取一个类的静态变量引用,并将其引用推到操作数栈中。指令获取静态变量用常量池中第#4常量来表示,该常量类型是一个字段的符号引用。这里获取的是System类中PrintStream字段引用放入栈中。
-
2 aload_0:将局部变量表中第一个局部变量的引用推到栈顶。在Java虚拟机中对于非静态方法调用,会将当前对象引用放入到局部变量表中第一个局部变量位置。【调用方法指令前,需要将调用方法的对象加入到操作数栈中。此步骤是调用构造函数的一个中间步骤】
-
3 getfield:getfield指令用来获取栈顶元素(2步骤入栈元素)的指定成员变量,在此会将栈顶元素(2步骤入栈元素)出栈,并将获取成员变量入栈。这里指定成员变量用常量池中第#3常量来表示,该常量类型是一个字段的符号引用。这里用来获取当前对象this.field成员变量,将this对象出栈,将成员变量入栈。
-
4 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#5常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里含义是调用PrintStream对象println方法,由于方法中存在一个参数,此时会将栈顶的元素(3步骤入栈元素)作为参数,其值指向对象field成员变量引用,并从操作数中出栈,接着将新的栈顶元素作为方法调用对象(1步骤入栈元素),并从操作数中出栈。由于println方法不存在返回值,因而不存在入栈操作。
4 局部变量表
局部变量表用来存储字节码指令中参数和局部变量。在指令的操作过程中,我们会将局部变量表中的值作为参数写入操作数栈中,也会将操作数栈中计算的结果回写局部变量表中
从局部变量0中装载int类型值入栈
iload_0
//将栈顶引用类型值保存到局部变量0中。
astroe_0