类文件结构

一.简介

实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java虚拟机不和包括Java在内任何语言绑定,它只与Class文件这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。

Clojure(Lisp 语言的一种方言)、Groovy、Scala 等语言都是运行在 Java 虚拟机之上。下图展示了不同的语言被不同的编译器编译成.class文件最终运行在 Java 虚拟机之上。

图片

可以说.class文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。

二.Class文件结构

根据Java虚拟机规范,类文件由单个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];//属性表集合
}

根据Java虚拟机规范规定,Class文件格式采用一种类似C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号和表

  • 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值
  • 表是由多个无符号数或者其他表作为数据项构成的复合数据,所有表都习惯地以"_info"结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质就是一张表。

Class文件具体由以下几个构成:

  • 魔数
  • 常量池
  • 访问标志
  • 类索引、父类索引、接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

图片

2.1 魔数

  • Class文件的头4个字节,唯一作用是确定文件是否为一个可被虚拟机接受Class文件,固定为“0xCAFEBABE”。
  • 第5和第6个字节是次版本号,第7和第8字节是主版本号(0x0034为52,对应JDK版本1.8)Java的版本号是从45开始的,JDK1.1之后的每一个JDK大版本发布主版本号向上加1,高版本的JDK能向下兼容低版本的JDK。

图片

2.2 常量池

上述图中字节码,版本号后面就是常量池,常量池可以理解为class文件资源仓库,它是class文件结构中与其它项目关联最多的数据类型,也是占用class文件空间最大的数据项目之一,也是class文件结构中与其它项目关联最多的数据类型,也是占用class文件空间最大的数据项目之一,也是class文件中第一出现表类型数据项目。

由于常量池中常量的数量不是固定的,所以常量池入口需要放置一项u2类型的数据,代表常量池的容量计数。不过,这里需要注意的是,这个容器计数是从1开始的而不是从0开始,也就是说,常量池中常量的个数是这个容器计数-1。将0空出来的目的是满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义。class文件中只有常量池的容量计数是从1开始的,对于其它集合类型,比如接口索引集合、字段表集合、方法表集合等的容量计数都是从0开始的。

常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近Java语言常量,如文本字符串,final常量等,而符号引用则属于编译原理方面的概念,包括以下三种:

  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符号(Descriptor)
  • 方法的名称和描述符

不同于C/C++, JVM是在加载Class文件的时候才进行的动态链接,也就是说这些字段和方法符号引用只有在运行期转换后才能获得真正的内存入口地址。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建或运行时解析并翻译到具体的内存地址中。

类型名称解释数量
u4magic魔数1
u2minjor_version次版本号1
u2majior_version主版本号1
u2constant_pool_count常量池常量个数1
cp_infoconstant_pool常量池constant_pool_count-1
u2access_flags访问标记1
u2this_class类索引1
u2super_class父类索引1

常量池表结构。

图片

表结构又有不同的数据结构。

图片

2.3 访问标志

常量池结束后紧接着的两个字节代表访问标志,用来标识一些类或接口的访问信息,包括:这个Class是类还是接口;是否定义为public;是否定义为abstract;如果是类的话,是否被声明为final等。具体的标志位以及含义如下表:
在这里插入图片描述
图片

2.4 当前类索引,父类索引与接口索引集合

u2 this_class;//当前类 
u2 super_class;//父类 
u2 interfaces_count;//接口 
u2 interfaces[interfaces_count];//一个类可以实现多个接口

类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为 0。
接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按implents(如果这个类本身是接口的话则是extends) 后的接口顺序从左到右排列在接口索引集合中。

2.5 字段表集合

    u2             fields_count;//Class 文件的字段的个数
    field_info     fields[fields_count];//一个类会可以有个字段

字段表(field info)用于描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。

field info(字段表) 的结构

access_flags: 字段的作用域(public ,private,protected修饰符),是实例变量还是类变量(static修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。

name_index: 对常量池的引用,表示的字段的名称;

descriptor_index: 对常量池的引用,表示字段和方法的描述符;

attributes_count: 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;

attributes[attributes_count]: 存放具体属性具体内容。

图片

字段的 access_flags 的取值
| 标志名称 | 标志值 | 含义 |
|:----😐:----😐:----😐:----😐:----😐:----😐
| ACC_PUBLIC | 0x00 01 | 字段是否为public |
| ACC_PRIVATE | 0x00 02 | 字段是否为private |
| ACC_PROTECTED | 0x00 04 | 字段是否为protected |
| ACC_STATIC | 0x00 08 | 字段是否为static |
| ACC_FINAL | 0x00 10 | 字段是否为final |
| ACC_VOLATILE | 0x00 40 | 字段是否为volatile |
| ACC_TRANSTENT | 0x00 80 | 字段是否为transient |
| ACC_SYNCHETIC | 0x10 00 | 字段是否为由编译器自动产生 |
| ACC_ENUM | 0x40 00 | 字段是否为enum |

2.6 方法表集合

    u2             methods_count;//Class 文件的方法的数量
    method_info    methods[methods_count];//一个类可以有个多个方法

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

method_info(方法表的) 结构

图片

标志名称标志值含义
ACC_PUBLIC0x00 01方法是否为public
ACC_PRIVATE0x00 02方法是否为private
ACC_PROTECTED0x00 04方法是否为protected
ACC_STATIC0x00 08方法是否为static
ACC_FINAL0x00 10方法是否为final
ACC_SYHCHRONRIZED0x00 20方法是否为synchronized
ACC_BRIDGE0x00 40方法是否是有编译器产生的方法
ACC_VARARGS0x00 80方法是否接受参数
ACC_NATIVE0x01 00方法是否为native
ACC_ABSTRACT0x04 00方法是否为abstract
ACC_STRICTFP0x08 00方法是否为strictfp
ACC_SYNTHETIC0x10 00方法是否是有编译器自动产生的

方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为"Code"的属性里面,属性表作为calss文件格式中最具扩展的一种数据项目.

在Java语言中,要重载一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名中,因此Java语言里面是无法仅仅靠返回值的不同来堆一个已有方法进行重载的.但是在class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存.也就是说,如果两个方法有相同的名称和特征签名,但是返回值不同,那么也是可以合法共存与同一个class文件中的。

2.7 属性表集合

   u2             attributes_count;//此类的属性表中的属性数
   attribute_info attributes[attributes_count];//属性表集合

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

参考
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
https://coolshell.cn/articles/9229.html
https://blog.csdn.net/luanlouis/article/details/39960815
《实战 Java 虚拟机》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

李孟聊人工智能

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

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

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

打赏作者

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

抵扣说明:

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

余额充值