# 字节码指令

在class字节码文件格式中,方法的字节码内容保存在方法的"Code"属性当中。

字节码文件就像protocol buffer等协议一样,定义的是二进制的传输协议,jvm可以解析这个数据形成更加立体的结构(比如类、字段、方法)

jvm在执行Java代码时,上通过线程进行调度、执行的,每个线程有各自的线程栈、程序计数器(program counter)

线程栈上一个栈结构,每个栈的元素即栈帧(Stack Frame)表示一个方法的调用,我们在debug时或者看到异常时,经常会看到这样的栈结构。 栈帧中具有局部变量表、操作数栈、返回地址、当前方法的引用等信息。

局部变量表(Local Variable)中包含了方法代码执行时的局部变量信息,包含了方法参数和方法中声明使用到的局部变量,局部变量表可以想象成一个数组, 数组的元素上slot,可以存放4字节的数据,所以long和double需要占用两个slot,而其他的primitive类型和引用类型都占用4字节。

操作数栈上方法执行过程中使用的栈结构,不要和线程栈混淆,操作数栈中可以中间结果,比如一个创建对象并赋值到一个局部变量引用的过程,会分为几步。

先是通过newinstance指令创建对象,这时栈顶会有创建出来的对象引用,还没有调用构造函数,然后通过dup对象复制引用,此时 栈顶的两个元素都是这个对象的引用,然后通过invokespecial指令调用构造函数,调用构造函数会弹出这个对象引用。 此时栈顶还剩一个该对象的引用,然后就可以通过局部变量操作将这个引用保存到局部变量表当中。

jvm指令提供了丰富的操作局部变量表和操作数栈的指令。

# jvm字节码执行概念模型

jvm规范定义了一套概念模型,虚拟机需要按照指定的规则来执行jvm字节码,各个虚拟机实现 可以在满足规范的条件下进行性能优化,比如一个比较著名的优化是JIT(Just in time compile)优化,即会将符合条件的热点代码编译成本地机器码执行,加快执行速度。

jvm通过线程维度调度执行代码,线程执行的代码对应于java.lang.Thread对象,线程启动后, jvm会调用线程的run方法,run方法中又可以继续调用其他方法,形成方法栈。

因为cpu需要各个线程之间执行代码,保证每个线程都能执行,也就是多线程并发,所以cpu会通过时间片的方式在各个线程间 调度,所以一个线程的任务会可能在执行一半就切换到其他线程上了,需要保存之前执行时的状态,以便下次cpu切换回来还能继续执行。

这个状态在jvm规范中包含了pc(program counter程序计数器)和Java虚拟机栈。

每个线程都有属于自己的pc和Java虚拟机栈。 程序计数器保存的是当前线程执行的方法的字节码位置,如果方法是native方法,则规范中没有定义。

Java虚拟机栈中保存方法栈帧,当线程中在执行的代码中调用了其他方法,就会在当前线程的栈帧中增加一个新的线程栈来执行调用的方法的代码。 方法执行完成(正常完成或异常退出)后会退出栈顶的方法继续执行上一个方法。

每个栈帧中的核心数据是操作数栈和局部变量表。 操作数栈是单个方法执行过程中需要的栈,因为在方法执行过程中会对数据进行操作(比如数字操作、引用操作等等)。 局部变量表用于保存方法执行过程中使用到的局部变量,方法的参数也会初始化到局部变量表中使用,实例方法(也就是非静态方法)的局部变量表的第一个是this对象引用。

每个方法执行时需要的方法栈的大小在编译后就固定下来(在方法的字节码属性中有max_local, max_stack属性)。

而线程栈的大小,则是不固定的,因为线程执行的方法可能会随着输入参数等情况发生变化,线程栈大小通过-Xss启动参数指定,比如-Xss1M表示每个线程的线程栈大小为1MB。 设置时不能太小也不能太大,太小容易抛出StackOverflowException,太大则会导致线程占用比较多的内存空间尤其是线程比较多的情况下。

jvm在执行方法字节码时,逻辑可以理解如下

读取方法的Code属性,读取max_stack, max_local创建对应的操作数栈和局部变量表。 从code数组读取字节码指令,解析指令并执行。 字节码指令是一个byte长度表示的指令,最多255个(0没使用)。有的指令不需要参数(比如dup, swap, iconst_0),有的指令还跟着参数(invokevirtual、istore)。 jvm就按照指令和参数,来操作操作数栈和局部变量表,如果遇到调用方法,则会创建新的方法栈然后取执行新的方法,方法执行完成后弹出当前方法栈, 继续执行之前的方法。

while(1) {
    fetch opcode and operand
    execute
    update_pc
}
1
2
3
4
5
Code_attribute {
       u2 attribute_name_index;
       u4 attribute_length;
       u2 max_stack;
       u2 max_locals;
       u4 code_length;
       u1 code[code_length];
       u2 exception_table_length;
       {   u2 start_pc;
           u2 end_pc;
           u2 handler_pc;
           u2 catch_type;
       } exception_table[exception_table_length];
       u2 attributes_count;
       attribute_info attributes[attributes_count];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 指令类型

# 操作局部变量表

istore astore iinc

# 操作操作数栈

iload iconst ldc sipush putfield getfield putstatic getstatic swap pop dup

# 方法调用

invokevirtual invokespecial invokestatic invokeinterface invokedynamic

# 计算

iadd imul idiv ineg

# 逻辑控制

if_comp ifgt iflt ifnull ifnonnull athrow

# 其他

monitorenter monitorexit instanceof new newarray anewarray return ireturn areturn

Java代码执行过程、线程、操作数栈、本地变量表。

while和for循环会编译成什么字节码

jdk代码中的一些优化(复制成局部变量)