类文件结构

类文件结构

类文件概述

JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件,即类文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。

字节码并不针对一种特定的机器,因此 Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。

类文件结构

根据 JVM 规范,Class 文件通过 ClassFile 定义:

ClassFile {
    u4             magic; // Class 文件的标志
    u2             minor_version; // Class 的小版本号
    u2             major_version; // Class 的大版本号
    u2             constant_pool_count; // 常量池的数量
    cp_info        constant_pool[constant_pool_count-1]; // 常量池
    u2             access_flags;// Class 的访问标记
    u2             this_class; // 当前类
    u2             super_class; // 父类
    u2             interfaces_count; // 接口
    u2             interfaces[interfaces_count]; // 一个类可以实现多个接口
    u2             fields_count; // Class 文件的字段属性
    field_info     fields[fields_count]; // 一个类会可以有多个字段
    u2             methods_count; // Class 文件的方法数量
    method_info    methods[methods_count]; // 一个类可以有个多个方法
    u2             attributes_count; // 此类的属性表中的属性数
    attribute_info attributes[attributes_count]; // 属性表集合
}

通过分析 ClassFilee,得到 class 文件的组成:

魔数

每个 Class 文件的头 4 个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接收的 Class 文件

文件版本号

紧接着魔数的四个字节存储的是 Class 文件的版本号:第 5 和第 6 字节位是次版本号(Minor Version),第 7 和第 8 字节位是主版本号(Major Version)。

每当 Java 发布大版本(比如 Java 8,Java 9)的时候,主版本号都会加 1。

可以使用 javap -v 命令来快速查看 Class 文件的版本号信息。

高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。在开发时要确保开发的的 JDK 版本和生产环境的 JDK 版本保持一致。

常量池

紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1。

这是因为常量池计数器是从 1 开始计数的,将第 0 项常量空出来是有特殊考虑的,索引值为 0 代表“不引用任何一个常量池项。

常量池主要存放两大常量:字面量符号引用

字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。

符号引用则属于编译原理方面的概念,包括下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

常量池中每一项常量都是一个表:表开始的第一位是一个 u1(占 1 个字节) 类型的标志位 - tag 来标识常量的类型,代表当前这个常量属于哪种常量类型。

访问标志

紧接着常量池的两个字节代表访问标志(Access Flag),这个标志用于识别一些类或者接口层次的访问信息,包括:

  • 这个 Class 是类还是接口
  • 是否为 public 或者 abstract 类型
  • 如果是类的话是否声明为 final 等

当前类 & 父类

类索引(This Class)用于确定这个类的全限定名,父类索引(Super Class)用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 Java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为 0。

接口索引集合

接口索引集合用来描述这个类实现了哪些接口,这些被实现的接口将按 implements (如果这个类本身是接口的话则是extends)后的接口顺序从左到右排列在接口索引集合中。

字段表集合

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

字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。

field_info{
    u2                access_flags;
    u2                name_index;  
    u2                descriptor_index;
    u2                attributes_count;
    attribute_info    attributes[attributes_count];
}

其中:

  • access_flags:字段的作用域(public,protected,private);实例变量还是类变量(static);是否可被序列化(transient);可变性(final);可见性(volatile)
  • name_index:对常量池的引用,表示字段的名称
  • descriptor_index:对常量池的引用,表示字段和方法的描述符
  • attributes_count:一个字段还会拥有一些额外的属性,表示额外属性的个数
  • attributes[attributes_count]:存放属性具体内容。

方法表集合

methods_count 表示方法的数量,而 method_info 表示方法表。

Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。

因为 volatile 修饰符和 transient 修饰符不可以修饰方法,所以方法表的访问标志中没有这两个对应的标志,但是增加了 synchronized、native、abstract 等关键字修饰方法,所以也就多了这些关键字对应的标志。

属性表集合

在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,JVM 运行时会忽略掉它不认识的属性。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vighzhen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值