深入理解JVM(六)——类文件结构——code

本文详细介绍了Java程序中方法体的字节码指令存储过程,解释了Code属性在Class文件中的作用及其结构组成,包括操作数栈、局部变量表、异常处理表等内容,并列举了各类字节码指令。

Code

Java程序中方法体中的代码经过Javac编译器处理之后,最终变成字节码指令存储在Code属性内。

Code属性出现在方法表的属性集合之中,但不是所有的方法表都必须有,譬如接口或者抽象类。

Code是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码(Code,方法体里面的Java代码)和元数据(MetaData,包括类,字段,方法定义以及其它信息)两部分

类型名称数量说明
u2attribute_name_index1指向constant_Utf8_info型常量的索引,常量值固定位“Code”,代表该属性的属性名称
u4attribute_length1属性值的长度
u2max_stack1操作数栈深度的最大值,方法执行的任何时刻不会超过这个深度,JVM根据这个值分配栈帧中操作数的深度
u2max_locals1局部变量表所需的存储空间,单位Slot,4个字节,不超过32位的数据类型,每个局部变量占1个Slot,64位的用两个Slot存放。包括this,异常处理参数,方法签名参数等。Slot可以重用(超过局部变量的作用域)。
u4code_length1字节码的长度
u1codecode_length字节码指令的一系列字节流,指令的意思及参数,u1类型的长度,最多有256条指令
u2exception_table_length1显示异常处理表集合,见下面的说明
exception_infoexception_tableexception_table_length1
u2attribute_count11
attribute_infoattributesattribute_count1

Java虚拟机执行字节码是基于栈的体系结构

Javac编译器编译的时候会把this关键字的访问转变成一个普通方法参数的访问,只是虚拟机调用实例方法时自动传入参数而已。因此局部变量表至少会有一个指向当前对象实例的局部变量。

异常表实际上是Java代码的一部分,编译器使用异常表而不是简单的跳转命令(jsr,ret已经不用了)来实现java异常及finally处理机制。
异常处理表:有4个字段,因为较好理解所以不画表格了start_pc,end_pc,catch_pc,handler_pc
如果代码在第start_pc行到第end_pc行之间(不包括end_pc),出现了类型为catch_type或者其子类的异常(catch_type指向一个constant_class_info型常量的索引),则转到第handler_pc行继续处理。当catch_type的值为0时,代表任意异常情况都需要转向handler_pc处进行处理。

字节码指令介绍

Java虚拟机的指令由一个字节长度的,代表着某种特定操作含义的数字(操作码)以及跟随其后的零至多个代表此操作所需参数(操作数)而构成。

由于Java虚拟机采用面向操作数栈而非寄存器的架构,所以大多数的指令都不包括操作数,而只有一个操作码。

Java虚拟机的指令集中,大多数的指令都包含了其操作对应的数据类型信息。如iload指令用于从局部变量表中加载int型的数据到操作数栈中,fload则是加载float型的数据。

i代表int类型
l代表long
s代表short
b代表byte
c代表char
f代表float
d代表double
a代表reference
但是之前说过指令集的数量只有一个字节,编译器会在编译期或者运行期将byte和short类型的数据带符号扩展为相应的int类型数据,将boolean和char类型零位扩展为相应的int型数据类型。

加载和存储指令

将一个局部变量加载到操作栈:
iload,iload_< n>,
lload,lload_< n>,
fload,fload_< n>,
dload,dload_< n>,
aload,aload_< n>

将一个数值从操作数栈存储到局部变量表
istore,istore_< n>,
lstore,lstore_< n>,
fstore,fstore_< n>,
dstore.dstore_< n>,
astore,astore_< n>

将一个常量加载到操作数栈
bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_m1,
iconst_< i>,lconst_< i>,fconst_< l>,dconst_< d>

扩充局部变量表的访问索引的指令:wide

运算符指令

加法指令:iadd,ladd,fadd,dadd
减法指令:isub,lsub,fsub,dsub
乘法指令:imul,lmul,fmul,dmul
除法指令:idiv,ldiv,fdiv,ddiv
求余指令:irem,lrem,frem,drem
取反指令:ineg,leng,fneg,dneg
位移指令:ishl,ishr,iushr,lshl,lshr,lushr
按位或指令:ior,lor
按位与指令:iand,land
按位异或指令:ixor,lxor
局部变量自增指令:iinc
比较指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp

类型转化指令

虚拟机在处理窄化类型转换时,必须显示地使用转化指令
i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f

对象创建与访问指令

创建类实例的指令 new
创建数组的指令 newarray anewarray multitianewarray
访问类字段(static字段,或称为类变量),实例字段的指令:
getfield putfield getstatic putstatic
把一个数组元素加载到操作数栈的指令:baload caload saload iaload laload faload daload aalaod
将一个操作数栈的值存储到数值元素的指令:bastore castore sastore iastore lastore fastore dastore aastore
取数组的长度的指令:arraylength
检查类实例类型的指令 instanceof checkcast

操作数栈转移指令

出栈一个或两个元素 pop pop2
复制栈顶一个或两个元素并将复制值重新压入栈dup dup2 dup_x1 dup2_x1 dup_x2 dup2_x2
栈顶元素互换 swap

控制转移指令

修改寄存器的值的指令
条件分支 ifeq iflt ifle ifne ifgt ifge ifnull ifnonnull …
复合条件分支 tableswitch lookupswitch
无条件分支 goto goto_w jsr jsr_w ret

方法调用和放回指令

invokevirtual 指令调用对象的实例方法
invokeinterface 调用接口的方法
invokespecial 调用一些特殊处理的实例方法(初始化,私有方法,父方法)
invokestatic 调用类方法(static)
invokedynamic 用于运行时动态解析出调用点限定符所引用的方法,并执行该方法

异常处理指令

现在Java虚拟机中用异常处理表来完成

同步指令

monitorenter
monitorexit
通常用来支持Java语言中的synchronized关键字的语义

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值