属性表中各个属性都有固定的格式,如下图所示:
- u2的属性名称索引(在常量池中的位置)
- u4属性表内容的长度
- u1具体的属性内容
Code属性
Code属性是整个Class文件中最重要的属性,Code属性只作用于方法表中,在Code属性中存储了Java方法体经过编译后Java的字节码指令,Code属性的结构如下:
max_stack
操作数栈的最大深度。虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度。
max_locals
局部变量表所需要的空间,单位是Slot,对于int、byte和returnAddress不超过32位的数据类型采用1个Slot来存储,而double和long等64位的数据类型采用两个Slot存储。
方法中的参数(实例方法还有一个隐藏的this),显示异常处理参数(try-cacatch中的异常)、方法体中的局部变量都需要用局部变量表存储。
code_length
code_length存储了字节码指令的长度,虽然长度是4个字节(表面也就是说字节码指令的长度可以达到2^32-1),但实际上Java虚拟机规定了方法体中的字节码指令最多有65535条。
code
code就存储了具体的字节码指令,具体的字节码指令我们可以不用强记,在使用的时候根据字节码去查表就可以,下图是其中一部分字节码指令:
exception_table_length
显示异常(受检查的异常)中的个数(try-cacatch中的异常)
exception_info
这是受检查异常的具体信息,这个字段不一定存在(如果没有try-cache),下图是显示异常的具体结构:
当代码在start_pc和end_pc之间出现了类型为catch_type(指向常量池中一个CONSTANT_Class_info型常量的索引)的异常时,便转到handler_pc进行处理,如果catch_type为0,代表任何异常都需要转到handler_pc处进行处理。
Code属性实例分析
public class ClassTest {
public int inc() {
int m;
try {
m = 1;
return m;
} catch (Exception e) {
m = 2;
return m;
} finally {
m = 3;
}
}
}
下图是这段方法的Code属性:
通过源码我们知道这段方法体大约有三种执行方式,一种是无任何异常返回1,另一种是有Exception及子类异常返回2,另外是抛出未受检查的异常。
下面我们站在字节码执行的角度上来看一下这段方法是如何执行的,首先我们看一下无异常的情况:
无异常执行
关于字节码的含义大家可以去查表,这里就不一一解释了,无异常执行的字节码为0-7,下面我们来详解一下7个步骤:
- 0 iconst_1:将常量1推入操作数栈顶
- 1 istore_1:将操作数栈顶元素(这里就是1)保存到局部变量表中的第二个Slot中,为什么会是第二个原因很简单,因为这是一个实例方法,第一个Slot为this,所以Slot的操作都是从第二个开始
- 2 iload_1:将第二个Slot的元素(1)存放到操作数栈顶
- 3 istore_2:将操作数栈顶元素(1)保存到局部变量表中的第三个Slot
- 4 iconst_3:这里就是跑到了finally中的代码,将常量3推入操作数栈顶
- 5 istore_1:将操作数栈顶元素(3)保存到局部变量表中的第二个Slot中
- 6 iload_2: 将第三个Slot中的元素(1)放入操作数栈顶
- 7 ireturn:返回操作数栈顶元素1
通过上述分析,我们可以看出无异常时,返回的值为1。
当发生Exception及其子类异常
首先我们通过查看受检查异常表发现,如果0-4字节码指令将会转到第8个字节码指令去执行,也就是说代码所执行的字节码指令为0-4,8-16,下面我们来分析一下当出现Exception时8-16的字节码指令:
- 8 astore_2:将操作数栈顶引用型数据(Exception e)保存到局部变量表的第三个Slot中
- 9 iconst_2:将常量2推入操作数栈顶
- 10 istore_1:将操作数栈顶元素(2)保存到局部变量表的第二个Slot中
- 11 iload_1:将第二个Slot中的元素(2)推入操作数栈顶
- 12 istore_3:将操作数栈顶元素(2)存入局部变量表的第四个Slot中
- 13 iconst_3:这里就是跑到了finally中的代码,将常量3推入操作数栈顶
- 14 istore_1:将操作数栈顶元素(2)存入局部变量表的第2个Slot中
- 15 iload_3:将第四个Slot中的元素(2)推入操作数栈顶
- 16 ireturn:返回操作数栈顶元素2
当发生其他异常时
通过查看受检查异常表可以发现,在我们代码任何阶段发生未受检查的异常时,都会转到17个字节码指令执行,也就是说我们代码会执行17-23个字节码指令:
- 17 astore 4:将操作数栈顶的引用型数据(其他异常)保存到局部变量表的第五个Slot
- 19 iconst_3:这里就是跑到了finally中的代码,将常量3推入操作数栈顶
- 20 istore_1:将栈顶元素(3)保存到局部变量表的第二个Slot中
- 21 aload 4:将局部变量表中第5个Slot中的异常压入操作数栈顶
- 22 athrow:将操作数栈顶异常抛出