文章目录


对象的创建与访问指令

Java是面向对象的程序设计语言,虚拟机平台从字节码层面就对面向对象做了深层次的支持。有一系列指令专门用于对象操作,可进一步细分为创建指令、字段访问指令、数组操作指令、类型检查指令。

创建指令

一、创建指令

虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令:

1. 创建类实例的指令:

· 创建类实例的指令:new

· 它接收一个操作数,为指向常量池的索引,表示要创建的类型,执行完成后,将对象的引用压入栈。

2. 创建数组的指令:

· 创建数组的指令:newarray、anewarray、multianewarray。

· newarray:创建基本类型数组

· anewarray:创建引用类型数组

· multianewarray:创建多维数组

上述创建指令可以用于创建对象或者数组,由于对象和数组在Java中的广泛使用,这些指令的使用频率也非常高。

字段访问指令

二、字段访问指令

对象创建后,就可以通过对象访问指令获取对象实例或数组实例中的字段或者数组元素。

· 访问类字段(static字段,或者称为类变量)的指令:getstatic、putstatic

· 访问类实例字段(非static字段,或者称为实例变量)的指令:getfield、putfield

举例:

以getstatic指令为例,它含有一个操作数,为指向常量池的Fieldref索引,它的作用就是获取Fieldref指定的对象或者值,并将其压入操作数栈。

public void sayHello() {
System.out.println("hello");
}

对应的字节码指令:

0 getstatic #8 <java/lang/System.out>

3 ldc #9 <hello>

5 invokevirtual #10 <java/io/PrintStream.println>

8 return

数组操作指令

三、数组操作指令

数组操作指令主要有:xastore和xaload指令。具体为:

• 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload

• 将一个操作数栈的值存储到数组元素中的指令:bastore、 castore、 sastore、iastore、 lastore、fastore、dastore、aastore

即:

教你如何直接阅读一个java的字节码指令(二)_操作数

• 取数组长度的指令:arraylength

• 该指令弹出栈顶的数组元素,获取数组的长度,将长度压入栈。

2. 说明

· 指令xaload表示将数组的元素压栈,比如saload、caload分别表示压入short数组和char数组。指令xaload在执行时,要求操作数中栈顶元素为数组索引i,栈顶顺位第2个元素为数组引用a,该指令会弹岀栈顶这两个元素,并将a[i]重新压入栈。

· xastore则专门针对数组操作,以iastore为例,它用于给一个int数组的给定索引赋值。在iastore执行前,操作数栈顶需要以此准备3个元素:值、索引、数组引用,iastore会弹出这3个值,并将值赋给数组中指定索引的位置。

类型检查指令

四、类型检查指令

检查类实例或数组类型的指令:instanceof、checkcast。

· 指令checkcast用于检查类型强制转换是否可以进行。如果可以进行,那么checkcast指令不会改变操作数栈,否则它会抛出ClassCastException异常。

· 指令instanceof用来判断给定对象是否是某一个类的实例,它会将判断结果压入操作数栈。

方法调用与返回指令

方法调用指令

1.方法调用指令:invokevirtual、invokeinterface、invokespecial、invokestatic 、invokedynamic

以下5条指令用于方法调用:

• invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),支持多态。这也是Java语言中最常见的方法分派方式。

• invokeinterface指令用于调用接口方法,它会在运行时搜索由特定对象所实现的这个接口方法,并找出适合的方法进行调用。

• invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(构造器)、私有方法和父类方法。这些方法都是静态类型绑定的,不会在调用时进行动态派发。

• invokestatic指令用于调用命名类中的类方法(static方法)。这是静态绑定的。

• invokedynamic:调用动态绑定的方法,这个是JDK 1.7后新加入的指令。用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。前面4条调用指令的分派逻辑都固化在 java 虚拟机内部,而 invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

· 代码举例

import java.util.Comparator;

/**
* @author wuwei
* @create 10.10
*/
public class MethodInvokeTest {
public static void main(String[] args) {
Father f = new Father();
Son s = new Son();
System.out.println(f.getInfo());
System.out.println(s.getInfo());

Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return 0;
}
};
//comparator = Integer::compare;

comparator.compare(12,32);
}
}

class Father {
private String info = "wuwei";

public void setInfo(String info) {
this.info = info;
}

public String getInfo() {
return info;
}
}

class Son extends Father {
private String info = "wuwei";

public void setInfo(String info) {
this.info = info;
}

public String getInfo() {
return info;
}

}

方法返回指令

2.方法返回指令:

方法调用结束前,需要进行返回。方法返回指令是根据返回值的类型区分的。

· 包括ireturn(当返回值是 boolean、byte、char、short和int 类型时使用)、lreturn、freturn、dreturn和areturn

· 另外还有一条return 指令供声明为 void的方法、实例初始化方法以及类和接口的类初始化方法使用。

教你如何直接阅读一个java的字节码指令(二)_操作数_02

举例:

通过ireturn指令,将当前函数操作数栈的顶层元素弹出,并将这个元素压入调用者函数的操作数栈中(因为调用者非常关心函数的返回值),所有在当前函数操作数栈中的其他元素都会被丢弃。

如果当前返回的是synchronized方法,那么还会执行一个隐含的monitorexit指令,退出临界区。

最后,会丢弃当前方法的整个帧,恢复调用者的帧,并将控制权转交给调用者。

教你如何直接阅读一个java的字节码指令(二)_java_03

对应的代码:

public int methodReturn(){
int i = 500;
int j = 200;
int k = 50;

return (i + j) / k;
}

操作数栈管理命令

操作数栈管理指令

如同操作一个普通数据结构中的堆栈那样,JVM提供的操作数栈管理指令,可以用于直接操作操作数栈的指令。

这类指令包括如下内容:

· 将一个或两个元素从栈顶弹出,并且直接废弃: pop,pop2;

· 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶: dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2;

· 将栈最顶端的两个Slot数值位置交换: swap。Java虚拟机没有提供交换两个64位数据类型(long、double)数值的指令。

· 指令nop,是一个非常特殊的指令,它的字节码为0x00。和汇编语言中的nop一样,它表示什么都不做。这条指令一般可用于调试、占位等。

这些指令属于通用型,对栈的压入或者弹出无需指明数据类型。

说明:

· 不带_x的指令是复制栈顶数据并压入栈顶。包括两个指令,dup和dup2。dup的系数代表要复制的Slot个数。

· dup开头的指令用于复制1个Slot的数据。例如1个int或1个reference类型数据

· dup2开头的指令用于复制2个Slot的数据。例如1个long,或2个int,或1个int+1个float类型数据

· 带_x的指令是复制栈顶数据并插入栈顶以下的某个位置。共有4个指令,dup_x1, dup2_x1, dup_x2, dup2_x2。对于带_x的复制插入指令,只要将指令的dup和x的系数相加,结果即为需要插入的位置。因此

· dup_x1插入位置:1+1=2,即栈顶2个Slot下面

· dup_x2插入位置:1+2=3,即栈顶3个Slot下面

· dup2_x1插入位置:2+1=3,即栈顶3个Slot下面

· dup2_x2插入位置:2+2=4,即栈顶4个Slot下面

· pop:将栈顶的1个Slot数值出栈。例如1个short类型数值

· pop2:将栈顶的2个Slot数值出栈。例如1个double类型数值,或者2个int类型数值