深入理解JVM-笔记4-Class文件

279 阅读3分钟

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文件, 及其字段表, 方法表都有自己的属性表集合

属性计数值
属性表

除预定义属性外, 还可自定义属性

  1. Code

方法表的attributes中有Code属性, 是方法的代码存储结构, 其本身也是一个表:

Code属性表:

  • attribute_name_index 即为code
  • max_stack 方法的对应一个虚拟机栈的栈帧, 栈帧中的操作栈深度
  • max_locals 局部变量表的大小, Slot个数(double,long占2slots)
  • code_length 方法字节码长度, 虽为u4,但java对顶方法不能超过2^32 = 65535
  • code 方法字节码指令
  1. Exceptions

方法中可能抛出的checked exceptions构成的表

  1. LineNumberTable

java源码与字节码行号的对应关系, 可以在javac中用参数取消生成

如果取消, 抛异常时不知源码行号, 也无法断点调试

  1. LocalVariableTable和LocalVariableTypeTable

都是记录栈帧中局部变量表与Java源码变量的对应关系, 一个记录描述符, 一个记录特征签名, 因为泛型的描述符是一样的, 需要用特征签名区分