JVM-9.Class类文件结构

  • Class 文件是一组以8个字节为基础额二级制流
  • 各数据项目严格按照顺序紧凑的排列在Class文件中,中间没有任何分隔符
  • Class文件的伪结构只包含两种数据类型:
    • 无符号数,以u1、u2、u4、u8来代表1个字节,2个字节,4个字节,8个字节的无符号数,用来描述数字,索引引用,数量值或者按照UTF-8编码构成字符串值
    • 表,由多个无符号数或者其他表作为数据项构成的复合数据类型,以_info结尾,用于描述具有层次关系的复合结构数据
  • 整个Class文件本质上就是一张表:
名称 类型 数量
magic u4 1
minor_version u2 1
major_version u2 1
constant_pool_count u2 1
constant_pool cp_info constant_pool_count-1
access_flags u2 1
this_class u2 1
super_class u2 1
interfaces_count u2 1
interfaces u2 interfaces_count
fields_count u2 1
fields field_info fields_count
u2 methods_count 1
methods method_info methods_count
attributes_count u2 1
attributeds attributed_info attributes_count

1. magic(魔法数)

  • 用于确认这个文件是否为一个能被虚拟机接受的Class文件。
  • 值为:0xCAFEBABE-(咖啡宝贝)

2. 版本号

  • minor_version 次版本号
  • major_version 主版本号
  • JDK向下兼容,向上不兼容

3. 常量池

  • constant_pool_count:常量池容量计数值
    • 计数从1开始,第0项空出来
  • 常量池主要用来存放字面量(Literal)和符号引用(Symbolic Reference)
    • 字面量如文本字符串,声明为final的常量
    • 符号引用包括:
      • 类和接口的全限定名(Full Qualified Name)
      • 字段的名称和描述符(Descriptor)
      • 方法的名称和描述符
  • 常量池中每一个常量都是一个表
    • 表结构各不相同
    • 都有一个u1类型的标志位(tag),代表当前常量属于的数据类型
  • javap -verbose Class可以输出常量表
常量池中常量项结构总表1

常量池中常量项结构总表2

4. 访问标志

访问标志(access_flag)用来识别一些类或者接口层次的访问信息:


访问标志

5. 类索引、父类索引和接口索引集合

  • 类索引(this_class)、父类索引(super_class)都是u2类型数据,接口索引(interfaces)是一组u2类型的集合。
  • 这三个数据来确定这个类的继承关系。
    • 类索引用于确定这个类的全限定名
    • 父类索引用于确认这个类的父类全限定名
      • 除了java.lang.Object之外的Java类都有父类,因此除了它之外的所有Java类的父类索引都不为0
  • 接口索引描述了类实现的接口按implements(或extends)后的接口顺序从左到右排列在索引集合中

6. 字段表集合

  • 字段表(field_info)用于描述接口或者类中声明的变量。

  • 包含类级变量以及实例级变量,不包括方法内部声明的局部变量

  • 变量修饰符用布尔值标识,变量名、数据类型用常量池中的常量表示

  • 字段表结构:


    字段表结构

    -access_flag是字段修饰符,取值如下:


    字段访问标志
    • name_index和descriptor_index分别标识字段简单名称和字段方法的描述符:
      • 每一维度的数组类型用前值[字符表示
      • 描述方法按照先参数列表,后返回值的顺序,参数列表按照顺序放在一组小括号()内。
      • 在descriptor_index之后都跟随者一个属性列表集合用于存储额外信息
      • 字段表中不会列出从超类或者父接口中继承来的字段,但可能列出自动添加的额外字段


        描述符字段含义

7. 方法表集合

  • 方法表的结构和字段表接口类似,访问标志和属性表集合的可选项有区别
  • 方法里的代码,经过编译器编译成字节码指令后,存放在方法属性表集合的Code属性中
  • 有可能出现编译器自动添加的方法,例如实例构造器


    方法表结构
方法访问标志

8. 属性表集合

  • 属性表(attribute_info),在Class文件,字段表,方法表都可以携带自己的属性表集合,用于描述场景专有信息
  • 属性表不要求具有严格顺序
  • 不与已有属性名重复,任何编译器都可以向属性表中写入自定义属性信息,JVM会自动忽略不认识的属性


    JVM预定义属性1

    JVM预定义属性2
  • 每个属性都需要从常量池中引入一个CONSTANT_Utf8_info类型常量来表示
  • 属性值的结构完全自定义,只需要通过一个u4的长度属性来说明属性值占用位数
  • 属性表结构:


    属性表结构

8.1 Code属性

  • 方法体经过javac编译后,变成字节码存储在Code属性内
  • Code属性出现在方法表的属性集合中,接口或抽象类中的方法没有Code属性
  • Code属性表结构:


    Code属性表结构
    • attribute_name_index指向CONSTANT_Utf8_info型常量的索引,固定值为“Code”
    • attribute_length指示了属性值的长度,和attribute_name_index一共6字节,所以属性值的长度固定位整个属性表长度减去6字节
    • max_stack标识操作数栈(Operand Stacks)深度的最大值,在方法执行的任意时刻,操作数栈不会超过这个深度,JVM据此分配栈帧(Stack Frame)中的栈操作深度
    • max_locals代表局部变量表需要的存储空间,
      • 此值单位是Slot,长度不超过32位的数据类型用1个Slot,double和long 64位的数据类型需要两个Slot
      • 方法参数,显式异常处理的参数,方法体中定义的局部变量都在需要局部变量表存放
      • Slot可以重用,根据变量作用于分配,得到max_locals
    • code_length和code用来存放Java源程序编译后生成的字节码长度和字节流
      • 一个指令就是一个u1类型单字节,对应一个指令,后面可能跟参数
      • 如果有异常表,表结构为:
        -标识字节码在start_pc行(行,指字节码相对于方法体开始的偏移量)到第end_pc行(不含)之间出现了类型为catch_type或者其子类的异常,就转到第handler_pc行继续处理
      • catch_type值可以为0,此时任何异常情况都转到handler_pc行


        异常表属性结构

8.2 Exceptions属性

  • Exceptions属性不是Code属性中的异常表
  • 它的作用是列举方法中可能抛出的受检异常(Checked Exceptions)
  • 也就是throws关键字之后的异常
  • 表结构:


    受检异常表结构
    • number_of_exceptions标识方法可能抛出的异常种数
    • exception_index_table标识一种受检异常,是一个指向常量池中CONSTANT_Class_info型常量的索引

8.3 LineNumberTable属性

  • 用于描述Java源码行号和字节码行号(偏移量)之间的对应关系
  • 可以在javac中用 -g:none或者-g:lines选项来取消或者要求生成这项信息,默认生成,取消后抛出异常时堆栈无法显示出错的行号。也无法按照源码设置断点
  • 表结构为:


    LineNumberTable属性结构
    • line_number_table是一个数量为line_number_table_length,类型为line_number_info的集合,
    • line_number_info表包括start_pc和line_number两个u2类型数据项,前者是字节码行号,后者是Java源码行号

8.4 LocalVariableTable属性

  • 用于描述栈帧中局部变量和Java源码中定义的变量之间的关系
  • 默认生成到Class文件之中,可以在javac中使用-g:none或者-g:vars取消或者要求生成。如果取消,所有参数名称会消失
  • 属性表结构:


    LocalVariableTable属性表结构
    • 其中的local_variable_info项目代表了栈帧和源码中的局部变量的关联,其表结构为:


      local_variable_info项目结构
      • start_pc和length表示这个局部变量的生命周期开始的字节码偏移量和作用域范围长度
      • name_index和descriptor_index指向CONSTANT_Utf8_info型常量索引,标识局部变量的名称和它的描述符
      • index是这个局部变量在栈帧局部变量中Slot位置

8.5 SourceFile属性

  • 用于记录生成这个Class文件的源码名称
  • 可选 -g:none-g:source
  • 定长属性,结构为:


    SourceFile属性结构
    • sourcefile_index:指向常量池中CONSTANT_Utf8_info,其值为源码文件的文件名

8.6 ConstantValue属性

  • 通知JVM自动为静态变量赋值
  • 也就是static修饰的变量
  • 表结构:


    ConstantValue属性结构

8.7 InnerClass属性

  • 用于记录内部类和宿主类之间的关系
  • 结构:


    InnerClass属性结构
    • number_of_classes标识需要记录的内部类个数
    • 一个内部类一个inner_class_info表:


      inner_classes_info表结构
      • inner_class_info_index和outer_class_info_index都是指向常量池中CONSTANT_Class_info型的常量索引,分别代表内部类和宿主类的符号引用
      • inner_name_index指向CONSTANT_Utf8_info型,内部类名称,匿名内部类为0
      • inner_class_access_flags:内部类反问标志,取值范围:


        inner_class_access_flags标志

8.8 Deprecated和Synthetic属性

  • Deprecated:用于类,字段,方法,在代码中用@deprecated注解进行设置,标识不推荐使用
  • Synthetic:标识字段,方法不是Java源码,是编译器自动生成的
  • 结构都是:


    属性结构
    • attribute_length值为0x00000000

8.9 StackMapTable属性

  • 在JVM类加载的字节码验证阶段被新类型验证器(Type Checker)使用,目的在与代替之前比较消耗性能的基于数据流分析的类型推导验证器
  • 暂时看不懂

8.10 Signature属性

  • 可选定长属性
  • 出现在类,属性表,方法表结构的属性表中
  • 记录泛型签名信息
  • 表结构:


    Signature属性结构
    • signature_index对常量池的索引,CONSTANT_Utf8_info型,表示类签名,方法类型签名或者字段类型签名

8.11 BootstrapMethods属性

  • 用于保存invokedynamic指令引用的引导方法限定符
  • 结构:


    BootstrapMethods属性结构
    • bootstrap_methods结构:


      bootstrap_method属性的结构
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 类文件结构 各种不同平台的虚拟机与所有平台都统一使用的程序存储格式— 字节码( ByteCode ) 是构成平台无...
    好好学习Sun阅读 3,627评论 0 0
  • 在说道 JVM 虚拟机的时候,很多人都会想到 Java 语言,诚然,Java 语言和 JVM 虚拟机息息相关,但是...
    lijiankun24阅读 14,021评论 9 31
  •  每一个class文件都对应着唯一一个类或者接口的定义信息,但是相对地,类或者接口并不一定都必须定义在文件里(比如...
    SunnyMore阅读 11,377评论 0 1
  • 1. Get up, step out, and live fully. I once passed a bill...
    DavidLam阅读 962评论 0 0
  • 温一壶百年普洱,弹几把扬琴琵琶。在这场思无邪的诗卷里,是谁在杨柳依依之时负命出征?点点滴滴,终化成风。 ...
    书山图阅读 2,915评论 0 0