0
点赞
收藏
分享

微信扫一扫

【Jvm基础篇3】Class 文件结构



博客目录

  • 1.谈谈你对 class 文件的了解
  • 2.fields 和 attributes 区别
  • 3.DemoTest 解析
  • 4.class 文件中的魔数和主次版本号?
  • 5.为什么常量池计数器从 1 开始
  • 6.class 文件常量池中存放的是什么内容?
  • 7.常量池的项目类型?
  • 8.java 字段名和方法名长度限制?
  • 9.class 文件的访问标志作用?
  • 10.类索引,父类索引,接口索引集合作用
  • 11.class 文件的字段表存储的什么信息?
  • 12.class 文件的方法表存储的什么?
  • 13.class 文件中的属性表?


1.谈谈你对 class 文件的了解

类型

名称

数量

u4

magic

1

u2

minor_version

1

u2

major_version

1

u2

constant_pool_count

1

cp_info

constant_pool

constant_pool_count - 1

u2

access_flags

1

u2

this_class

1

u2

super_class

1

u2

interfaces_count

1

u2

interfaces

interfaces_count

u2

fields_count

1

field_info

fields

fields_count

u2

methods_count

1

method_info

methods

methods_count

u2

attributes_count

1

attribute_info

attributes

attributes_count

以上是 Java 类文件格式中的各个部分的名称、类型和数量的展示。其中:

  • u4 表示 4 个字节无符号整数
  • u2 表示 2 个字节无符号整数

constant_pool_count: 表示常量池中的常量数量(不包括占用两个位置的 long 和 double 类型常量,所以要减去 1)

在 Java 类文件格式中,常量池是一个重要的部分,它包含了类、接口、字段、方法等信息的符号引用和字面量常量。其他部分则包括类版本信息、访问标志、接口列表、字段表、方法表和属性表等。

2.fields 和 attributes 区别

在 Java 的 Class 文件中,fields(字段表)和attributes(属性表)是两个不同的部分,用于描述类或接口的结构和特性。

  1. Fields(字段表):
  • fields部分用于描述类或接口中定义的字段(成员变量)信息。
  • 每个字段都包含了字段的访问修饰符、字段名称、字段类型等信息。
  • 字段表中记录了类或接口中所有的字段,无论是静态字段还是实例字段,包括公共的、私有的、保护的和默认访问权限的字段。
  1. Attributes(属性表):
  • attributes部分用于描述类、字段、方法或代码等部分的额外属性信息。
  • 属性表中包含了各种不同类型的属性,这些属性可以用于存储额外的元数据,供 Java 虚拟机和其他工具使用。
  • 在类级别,属性表中可能包含SourceFile(源文件名)、InnerClasses(内部类信息)、EnclosingMethod(外部类和方法信息)等属性。
  • 在字段级别,属性表中可能包含ConstantValue(常量值)、Synthetic(合成字段标记)等属性。
  • 在方法级别,属性表中可能包含Code(字节码)、Exceptions(异常表)、LineNumberTable(行号表)等属性。

总结: fields部分记录了类或接口中定义的字段信息,而attributes部分用于描述类、字段、方法或代码等部分的额外属性信息。fields主要描述类的结构,而attributes主要用于存储附加的元数据和信息,用于支持 Java 虚拟机和其他工具的功能。

3.DemoTest 解析

class 文件是以一组 8 个字节为基础单位的二进制流,各个数据项严格按照顺序紧凑排列在文件中,中间没有任何分隔符,这使得 class 文件存储的都是程序运行的必要数据,没有空隙存在.

class 文件格式采用一种类似 c 语言结构体的伪结构体老存储数据,这种伪结构体只包含 2 种数据类型:无符号数和表

无符号数属于基本的数据类型,以 u1,u2,u4,u8 分别来表示 1 个字节,2 个字节,4 个字节,8 个字节的无符号数.无符号数可以用来描述数字,索引引用,数量值或者按照 utf-8 编码构成的字符串值.

表是由多个无符号数或者其他表作为数据项构成的复合型数据结构,为了便于区分,表通常以_info 结尾.用于描述复杂的结构,整个 class 文件可以看成一个大表.

【Jvm基础篇3】Class 文件结构_spring boot_02

ClassFile {
    u4             magic;//魔数
    u2             minor_version;//次版本号
    u2             major_version;//主版本号
    u2             constant_pool_count;//常量池数量
    cp_info        constant_pool[constant_pool_count-1];//常量池信息
    u2             access_flags;//访问标志
    u2             this_class;//类索引
    u2             super_class;//父类索引
    u2             interfaces_count;//接口数(2位,所以一个类最多65535个接口)
    u2             interfaces[interfaces_count];//接口索引
    u2             fields_count;//字段数
    field_info     fields[fields_count];//字段表集合
    u2             methods_count;//方法数
    method_info    methods[methods_count];//方法集合
    u2             attributes_count;//属性数
    attribute_info attributes[attributes_count];//属性表集合
}

4.class 文件中的魔数和主次版本号?

每个 class 文件的头 4 个字节被称为魔数,它的唯一作用是确定这个文件是否能为一个虚拟机所接受的 class 文件.

紧接着是 class 文件的版本号,第五和第六字节是次版本号,第七第八是主版本号,java 的版本号是从 45 开始的,jdk1.1 开始每个 jdk 大版本发布主版本号向上加 1

【Jvm基础篇3】Class 文件结构_spring boot_03

5.为什么常量池计数器从 1 开始

由于常量池中常量的数量是不固定的,所以常量池中的入口需要放置一项 u2 类型的数据,代表常量池容量计数器,constant_pool_count,这个计数器是从 1 开始的不是从 0 开始的,如下图所示,十六进制数 0x0016,十进制就是 22,代表着常量池中有 21 项常量,索引范围为 1~21.第 0 项表示不引用任何一个常量池项目.class 文件只有常量池的容量是从 1 开始的,对于其他的集合类型,包括接口索引集合,字段表集合,方法表集合等的容量计数器都是从 0 开始的.

  • 匿名内部类本身没有类名称,进行名称引用时,会将 index 指向 0
  • Object 类的 class 文件父类索引指向 0

【Jvm基础篇3】Class 文件结构_字段_04

6.class 文件常量池中存放的是什么内容?

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

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

符号引用则属于偏编译方面的概念,主要包含以下几类常量:

  • 被模块导出或者开放的包 package
  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符
  • 方法句柄和方法类型
  • 动态调用点和动态常量

【Jvm基础篇3】Class 文件结构_chrome插件_05

7.常量池的项目类型?

十四种常量都有自己的结构.

【Jvm基础篇3】Class 文件结构_intellij idea_06

CONSTANT_Class_info: 代表类或接口的符号引用.数据结构如下.

tag 标志位用于区分常量类型,name_index 是一个索引值,指向常量池中一个 CONSTANT_Utf8_info 类型的常量.此常量代表了这个类或接口的全限定名.

【Jvm基础篇3】Class 文件结构_chrome插件_07

CONSTANT_Utf8_info: 的数据结构如下

  • tag 是标志位区分类型
  • length 代表数据长度,最大为 65535
  • bytes 代表实际数据

【Jvm基础篇3】Class 文件结构_spring boot_08

常量

项目

类型

描述

CONSTANT_Utf8_info

tag

u1

值为 1

length

u2

UTF-8 编码的字符串占用的字节数

bytes

u1

长度为 length 的 UTF-8 编码的字符串

CONSTANT_Integer_info

tag

u1

值为 3

bytes

u4

按照高位在前存储的 int 值

CONSTANT_Float_info

tag

u1

值为 4

bytes

u4

按照高位在前存储的 float 值

CONSTANT_Long_info

tag

u5

值为 5

bytes

u8

按照高位在前存储的 long 值

CONSTANT_Double_info

tag

u1

值为 6

bytes

u8

按照高位在前存储的 double 值

CONSTANT_Class_info

tag

u1

值为 7

index

u2

指向全限定名常量项的索引

CONSTANT_String_info

tag

u1

值为 8

index

u2

指向字符串字面量的索引

CONSTANT_Fieldref_info

tag

u1

值为 9

index

u2

指向声明字段的类或者接口描述符 CONSTANT_Class_info 的索引项

index

u2

指向字段描述符 CONSTANT_NameAndType 的索引项

CONSTANT_Methodref_info

tag

u1

值为 10

index

u2

指向声明方法的类描述符 CONSTANT_ Class_info 的索引项

index

u2

指向名称及类型描述符 CONSTANT_NameAndType 的索引项

CONSTANT_Interface_Methodref _info

tag

u1

值为 11

index

u2

指向声明方法的接口描述符 CONSTANT_Class_info 的索引项

index

u2

指向名称及类型描述符 CONSTANT_NameAndType 的索引项

CONSTANT_NameAndType_info

tag

u1

值为 12

index

u2

指向该字段或方法名称常量项的索引

index

u2

指向该字段或方法描述符常量项的索引

CONSTANT_MethodHandle_info

tag

u1

值为 15

reference_kind

u1

值必须在 1~9 之间(包括 1 和 9)它决定了方法句柄的类型,方法句柄类型的值表示方法句柄的字节码行为

reference_index

u2

值必须是对常量池的有效索引

CONSTANT_MethodType_info

tag

u1

值为 16

descriptor_index

u2

值必须是对常量池的有效索引,常量池在该索引处的项必须是 CONSTANT Utf8info 结构,表示方法的描述符

CONSTANT_Invoke_Dynamic_info

tag

u1

值为 18

bootstrap_method_attr_index

u2

值必须是对当前 Class 文件中引导方法表的 bootstrap_methods 数组的有效索引

name_and_type_index

u2

值必须是对当前常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_NameAndType_info 结构,表示方法名和方法描述符

8.java 字段名和方法名长度限制?

字段名和方法名都存储在常量池中

存储这 2 个名称需要用到常量池中的 constant_utf8_info 类型来存储,以下是 constant_utf8_info 的存储结构,

【Jvm基础篇3】Class 文件结构_chrome插件_09

constant_utf8_info 的最大长度也是 java 中方法名字段名的长度,这里最大长度就是 length 的最大值,即 u2 类型能表达的最大值为 65535,所以 java 程序中定义了超过 64kb 英文字符的变量或者方法名,即使规则和名字符号全部合法,也无法编译.

  • 1kb=1024 字节
  • 64kb=65536 字节是临街值,不能等于 64kb

9.class 文件的访问标志作用?

常量池结束后,是 2 个字节的访问标志,用于标示类和接口层次的访问信息.包括这个 class 是类还是接口,是否定义为 public 类型,是否定义为 abstract 类型,如果是类的话,是否声明为 final 等等.

【Jvm基础篇3】Class 文件结构_intellij idea_10

【Jvm基础篇3】Class 文件结构_intellij idea_11

10.类索引,父类索引,接口索引集合作用

类索引(this_class)和父类索引(super_class)都是一个 u2 类型的数据.而接口索引集合(interfaces)是一组 u2 类型的数据集合,class 文件中由这三项数据来确定该类的继承实现关系.

【Jvm基础篇3】Class 文件结构_chrome插件_12

类索引用于确定这个类的全限定名(全限定名称存储于常量池),父类索引用于确定这个类的父类的全限定名.这里说的索引,是指向常量池中的 constant_class_info 类型,constant_class_info 又指向了 constant_utf8_info 从而确定全限定名.

由于 java 语言不支持多继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 java 类都有父类,且父类索引都不为 0,接口索引集合就是用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 关键字后的接口顺序从左到右排列在接口索引集合中.

类索引,父类索引,接口索引集合都按顺序排列在访问标志之后,类索引和父类索引,用 2 个 u2 类型的值表示,他们各自指向一个 constant_class_info 的类描述符常量,通过 constant_class_info 类型的常量中的索引值可以找到定义在 constant_utf8_info 类型的常量中的全限定名字符串.

【Jvm基础篇3】Class 文件结构_常量池_13

11.class 文件的字段表存储的什么信息?

【Jvm基础篇3】Class 文件结构_字段_14

字段表(field_info)用于描述接口或者类中声明的变量,java 语言中的字段包括类级变量以及实例级变量,但不包含在方法内部声明的局部变量.

字段表存储的是变量的修饰符+字段的描述符索引(索引指向常量池)+字段名称索引(索引指向常量池).

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

描述符:字段类型.

public final static String number=“1”,public final 和 static 是访问修饰符access_flags,这些都存在 class 文件的字段表中,String 是字段描述符,存放于常量池中的 name_index,number 是字段的名称 descrip_index,存放于常量池,这两部分的关联,是通过字段表的 name_index 指向常量池中的字段名称 numberdescrip_index 指向常量池中的描述符.

【Jvm基础篇3】Class 文件结构_chrome插件_15

12.class 文件的方法表存储的什么?

class 文件存储格式中对方法的描述,采用的方式和字段表一致,方法表的结构和字段表一致,包含访问标示access_flags,名称索引name_index,描述符索引descriptor_index,属性表集合attributes

【Jvm基础篇3】Class 文件结构_intellij idea_16

【Jvm基础篇3】Class 文件结构_spring boot_17

13.class 文件中的属性表?

【Jvm基础篇3】Class 文件结构_常量池_18

属性表在 class 文件,字段表,方法表都有自己的属性表集合,以描述某些场景专有的信息.与 class 文件中其他数据项要求严格的顺序,长度和内容不同,属性表限制稍微宽松一些,不再要求严格的顺序.只要属性名不重复,允许写入自己定义的属性信息.

对于每一个属性,它的名称都是从常量池中引用一个 constant_utf8_info 类型的常量来表示,而属性值的结构是完全自定义的,只需要通过一个 u4 的长度属性去说明属性值所占用的字节的位数即可.

【Jvm基础篇3】Class 文件结构_intellij idea_19

Code 属性:java 代码经过 javac 编译之后,最终变成字节码指令存储在 code 属性中.code 属性出现在方法表的属性集合中.但并非所有的方法表都必须存在这个属性,譬如接口和抽象类的方法就不存在 code 属性,如果方法表有 code 属性存在,那么它的结构如下:

【Jvm基础篇3】Class 文件结构_spring boot_20

LineNumberTable 属性:LineNumberTable 属性用于描述 java 源码的行号和字节码行号之间的偏移量的对应关系.它不是运行时必需的属性,但默认会生成到 class 文件之中,可以在 javac 中使用-g:none 或者-g:lines 选项来取消或者要求生成这项信息,如果选择不生成 LineNumberTable 属性,对程序的影响是,抛出异常时,堆栈中将不显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点.

【Jvm基础篇3】Class 文件结构_常量池_21

ConstantValue 属性:主要作用是为静态变量赋值.只有被 static 修饰的变量才能使用这个属性. int x=123 和 static int x=123 这样的定义在 java 中很常见,但虚拟机对这 2 种方式的赋值的方式和时刻有所不同.对于非 static 修饰的变量,在实例构造器 init 方法中进行赋值.对于类变量,有 2 种方式,通过类构造器的 clinit 方法赋值或者使用 ConstantValue 属性.如果同时使用 final 和 static 关键字修饰同一个变量,并且变量类型是基本数据类型或者 string,就会使用 ConstantValue 属性来进行初始化,如果没有用 final 修饰,或者非基本类型或者字符串,会在构造器 clinit 方法中初始化.

//举例说明
public static final String NUMBER=“111111”;

static 和 final 同时修饰的变量,会在编译的过程中把 NUBER 的值进行初始化,放到 ConstanValue 属性中,否则需要到最后一步初始化时在 clinit 方法中进行初始化.

【Jvm基础篇3】Class 文件结构_spring boot_22

觉得有用的话点个赞 👍🏻 呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

【Jvm基础篇3】Class 文件结构_intellij idea_23


举报

相关推荐

0 条评论