类文件结构
这一章节主要讲述了class数据结构的规范以及一些细节,不可避免的十分枯燥,但这部分也是jvm运行的重要基础,是深入了解jvm不可不接触的知识。
无关性的基石
class字节码实现平台无关性甚至语言无关性(包括除了java之外能运行在jvm上的语言:Clojure、Groovy、JRuby、Jython等)的基石,是一种字节码的存储格式,jvm不与包括Java在内的任何语言绑定,只与Class文件这种特定的二进制文件格式所关联,其中包含了jvm指令集、符号表以及若干其他辅助信息。
即jvm从不关心Class文件来源自何种语言。
Class类文件结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列,无任何分隔符。
Class文件采用一种类似C语言结构体的数据结构来存储数据,这种结构体中只有2中数据类型:无符号数和表。
魔数(Magic Number)与Class文件版本(Minor Version/Major Version)
每个Class文件头4个字节称为魔数(Magic Number),确定这个文件是否是Class文件,固定为0xCAFEBABE。
紧跟着魔数的4个字节存储了Class的版本号,JDK能向下兼容旧版本的Class文件,但不能向上兼容。
常量池(Constant Pool)
Class文件的资源仓库,与其他项目关联最多的数据类型。
前置了u2类型的常量池容量计数器(constant_pool_count),很奇葩,从1开始。
常量分为两大类:
字面量(Literal)
类似于Java语言的常量,如文本字符串、final值等。
符号引用(Symbolic References)
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
访问标志(access_flags)
是Class类还是接口,是否public,是否abstract,是否final等。
类索引、父类索引与接口索引集合
都是u2类型,各自指向CONSTANT_Class_info的类描述符常量,确定了这个类的全限定名、父类的全限定名,接口的全限定名集合。
字段表集合
描述接口或类中声明的变量,但不包括方法内部的局部变量。用一些修饰符(ACC_PUBLIC, ACC_STATIC, ACC_TRANSIENT等)的布尔值和常量池中的字段名、字段类型描述。
方法表集合
与字段表集合类似。
方法里面的Java代码,经过编译成字节码指令后,存在在方法属性表的一个名为Code的属性中。
编译器可能会自动添加一些方法,如类构造器
和实例构造器 属性表集合
- Code属性
- Exception属性
LineNumberTable属性
描述Java源代码与字节码行号之间的对应关系,用以在出错时借此显示出错行号,调试时根据源代码设置断点。
LocalVariableTable
描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,不是运行时必需。
如果禁用,方法参数名会消失,显示arg0,arg1等占位符,断点调试时也无法根据参数名从上下文中获得参数值。
SourceFile属性
- ConstantValue属性
- InnerClasses属性
- Deprecated/Synthetic属性
- StackMapTable属性
- Signature属性(泛型签名信息)
- BootstrapMethods属性
字节码指令简介
字节码与数据类型
很多运算类型都是先转换成相应的运算类型再进行操作,以减少指令数量。
加载和存储指令
- 运算指令
- 类型转换指令
- 对象创建与访问指令
- 操作数栈管理指令
- 控制转移指令
- 方法调用和返回指令
- 异常处理指令
- 同步指令
公有设计与私有实现
公有设计:Java虚拟机规范
私有实现:Java虚拟机实现
私有实现必须能够读取Class文件并精确实现包含在其中的jvm代码的语义,与此同时,一个优秀的虚拟机实现,在必须的满足虚拟机规范约束下,对具体实现做出修改和优化。
这也是软件世界乃至所有行业制定标准者对实现标准者的约束,与实现者对制定者的反推动的经典模型。