class类文件结构
数据及结构
- 是一组以8位字节为基础单位的二进制流。当遇到占有8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储
- 采用一种类似于C语言结构体的伪结构来存储数据
- 无符号数:u1 u2 u4 u8
- 表: 由多个无符号数或者其他表作为数据项构成的复合数据类型
文件格式
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u4 | magic | 1 | 魔数(0xCAFEBABY)-- 确定这个class文件是否能被虚拟机接受 |
u2 | minor_version | 1 | 次版本号 |
u2 | major_version | 1 | 主版本号 |
u2 | constant_pool_count | 1 | 常量池容量计数值 |
cp_info | constant_pool | constant_pool_count-1 | 常量池中每一项常量都是一个表(14种,jdk 1.7) 字面量 – 文本字符串、声明为final的常量值 符号引用 – 类和接口的全限定名、字段的名称和描述符、方法的名称和描述符 |
u2 | access_flags | 1 | 一共有16个标志位可以使用(当前之定义8个) 识别一些类或者接口层次的访问信息 ACC_PUBLIC、ACC_SUPER、ACC_FINAL、ACC_INTERFACE、ACC_ABSTRACT、ACC_SYNTHETIC、ACC_ANNOTATION、ACC_ENUM |
u2 | this_class | 1 | |
u2 | super_class | 1 | |
u2 | interfaces_count | 1 | |
u2 | interfaces | interfaces_count | |
u2 | fields_count | 1 | |
field_info | fields | fields_count | 包含类级变量和实例级变量(不包含局部变量) 字段表结构:access_flags(u2)、name_index(u2)、descriptor_index(u2)、attributes_count(u2)、attributes(attribute_info) 字段表集合中不会列出从超类或父接口中继承来的字段,但可能列出原本不存在的字段(譬如内部类为了保持对外部类的访问性,会自动添加指向外部类实例的字段) |
u2 | methods_count | 1 | |
method_info | methods | methods_count | 方法表结构同字段表一样 方法表中的代码经过编译器编译成字节码指令后,存放在方法属性表中的“Code”属性里 如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息,但同样有可能会出现编译器自动添加的方法,如类构造器"clinit"和实例构造器"init" |
u2 | attributes_count | 1 | |
attribute_info | attributes | attributes_count | 如Code、ConstantValue、InnerClasses等等 |
字节码指令简介
加载和存储指令 – 将数据在栈帧中的局部变量表和操作数栈之间来回传输
* 将一个局部变量表加载到操作数栈:iload、iload_n、lload、fload、dload、aload(对象)、...
* 讲一个数值从操作数栈存储到局部变量表:i(l/f/d/a)store、i(l/f/d/a)store_n
* 将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_i、lconst_l、fconst_f、dconst_d
* 扩充局部变量表的访问索引指令:wide
运算指令
* 加法指令:i(l/f/d)add
* 减法指令:i(l/f/d)sub
* 乘法指令:i(l/f/d)mul
* 除法指令:i(l/f/d)div
* 求余指令:i(l/f/d)rem
* 取反指令:i(l/f/d)neg
* 位移指令:ishl、ishr、iushr、lshl、lshr、lushr
* 按位或指令:ior 、lor
* 按位与指令:iand、land
* 按位异或指令:ixor、lxor
* 局部变量自增指令:iinc
* 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
- 补充说明:
- java 虚拟机规范没有明确定义过整型数据溢出的具体运算结果,除非求余及除法指令时除数为0抛出异常(ArithmeticException),其余任何整数运算场景都不抛出运行时异常
- 浮点数转为整数时,所有小数部分有效字节会被丢弃 – 向零舍入模式
- java虚拟机在处理浮点数运算时不会抛出任何运行时异常,当一个操作溢出时,将会使用有符号的无穷大来表示,如果某个操作没有明确的数学定义的话,将会使用NaN来表示。所有使用Nan值作为操作数的算术操作,结果都返回NaN。
System.out.println(-9.11 > 1.11); false
System.out.println((int)-9.11); -9
System.out.println((int)9.11); 9
System.out.println(Double.NaN +1); NaN
System.out.println(Double.NaN *2); NaN
System.out.println(Double.NaN /2); NaN
System.out.println(Double.NaN < 1); false
类型转换指令
对象创建与访问指令
- 创建类实例指令: new
- 创建数组指令: newarray、anewarray、multianewarray
- 访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令:getstatic、putstatic、getfield、putfield
- 把一个数组元素加载到操作数栈的指令:ba(ca/sa/ia/fa/da/aa)load
- 取数组长度的指令: arraylength
- 检查类实例类型的指令: instanceof、checkcast
操作数栈管理指令
- 将操作数栈的栈顶一个或两个元素出站:pop、pop2
- 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup_x2、dup2_x1、dup2_x2
- 将栈最顶端两个数值互换:swap
控制转移指令
- 条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、…
- 复合分支:tableswitch、lookupswitch
- 无条件分支:goto、goto_w、jsr、jsr_w、ret
方法调用和返回指令
- invokevirtual 用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派)
- invokeinterface 调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找到适合的方法进行调用
- invokespecial 用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法
- invokestatic 用于调用类方法(static方法)
- invokedynamic 用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法
- 返回指令:ireturn(当返回值是boolean、byte、char、short、int类型时使用)、lreturn、freturn、dreturn和areturn、return(声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用)
异常处理指令
java程序中显式抛出异常的操作(throw语句)都是由athrow指令来实现,除了用throw语句显式抛出异常情况之外,java虚拟机规范还规定了许多运行时异常会在其它java虚拟机指令检测到异常时自动抛出。
在Java虚拟机中,处理异常(catch语句)是采用异常表来完成的。