Class文件
JVM执行与Class文件绑定, 不与Java语言绑定
Java语言通过javac编译器编译为class字节码文件. 同样地, Groovy, JRuby, JPython, Scala, Kotlin, Clojure可以通过对应的编译器编译为class文件, 在JVM上执行.(语言编译器不包含在JRE中)
Class文件时以8个字节为单位的二进制流, 没有分隔符
Magic Number
每个Class文件的头4个字节, 用于确认文件类型为Class文件 0xCAFEBABE
Class文件版本号
4个字节, , JVM会拒绝比自身更高的Class文件
- 次版本号 2个字节.
- 主版本号 2个字节. 从45开始, 每个新JDK版本+1(JDK8->52)
常量池
常量池容量计数值
e.g.22. 表示有1-21共21项常量(0用于表示不引用常量池中的项), 每项常量代表一种类型的
常量
常量池中每一项都是一个表, 存对应的数据结构.
常量池中存放的类型包括:
- 字面量 即文本字符串, final的常量等
- 符号引用
- 被模块导出/开放的包
- 类和接口的全限定名
- 字段名称和描述符
- 方法名称和描述符
- 方法句柄和方法类型
- 动态调用点和动态常量
e.g. 类和接口的全限定名用一个name_index指向另一个存在常量池中的UTF8字符串字面量
可以用javap工具分析出Class文件中的字节码, 直接分析出每个常量的值
访问标志
类或接口的访问标志
2个字节, 共16位, 每位代表一种访问信息(public, final, interface, annotation, enum, module, super, abstract, synthetic等)
类信息
this_class
父类信息
super_class
接口信息
接口计数值
接口表 ...
字段信息
字段计数值
字段表
描述类/接口中声明的变量, 指类级别的, 不包括方法的局部变量
字段表包括:
- access_flags 字段访问标志符 u2 类似访问标志
- name_index 指向常量的简单名称索引 u2
- descriptor_index 字段描述符(字段类型) u2
- attributes_count u2
- attributes ...
内部类会自动添加指向外部类示例的字段
但子类不会含有父类继承来的字段(因为java中字段不能被继承, 子类不会含有与父类相同名称的字段)
方法信息
方法计数值
方法表
类似字段表:
- access_flags
- name_index
- descriptor_index (包括方法特征签名(即方法名称,参数顺序和类型)及返回值,受查异常表, 因此返回类型不同的方法可以共存)
- attributes_count
- attributes ...(其中一个属性CODE即为方法代码)
子类重写的方法会出现在子类的方法表中
属性信息
Class文件, 及其字段表, 方法表都有自己的属性表集合
属性计数值
属性表
除预定义属性外, 还可自定义属性
- Code
方法表的attributes中有Code属性, 是方法的代码存储结构, 其本身也是一个表:
Code属性表:
- attribute_name_index 即为code
- max_stack 方法的对应一个虚拟机栈的栈帧, 栈帧中的操作栈深度
- max_locals 局部变量表的大小, Slot个数(double,long占2slots)
- code_length 方法字节码长度, 虽为u4,但java对顶方法不能超过2^32 = 65535
- code 方法字节码指令
- Exceptions
方法中可能抛出的checked exceptions构成的表
- LineNumberTable
java源码与字节码行号的对应关系, 可以在javac中用参数取消生成
如果取消, 抛异常时不知源码行号, 也无法断点调试
- LocalVariableTable和LocalVariableTypeTable
都是记录栈帧中局部变量表与Java源码变量的对应关系, 一个记录描述符, 一个记录特征签名, 因为泛型的描述符是一样的, 需要用特征签名区分