JVM之Class字节码解析

本文详细解析了JVM中的Class文件格式,包括字节存储顺序、魔法数、版本号、常量池、访问标志、类索引、父类索引、接口索引集合、字段表集合、方法表集合和属性表集合。通过这些内容,阐述了Class文件如何存储各类信息,以及如何体现Java的特性与兼容性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Class Format

类文件就是由以下两种类型组成的

u(n) 表示存放n个无符号字节

info 一种复杂结构由无符号和其他表项组成

class二进制流数据组成项

名称类型数量
magic(魔法数)u41
minor_version(次版本)u21
major_version(主版本)u21
constant_pool_count(常量池的个数)u21
constant_pool_info(常量池)cp_infoconstant_pool_count
access_flagu21
this_class(当前类,常量池索引)u21
super_class(父类)u21
interface_countu21
interfaces(class 常量索引)u2interface_count
field_countu21
fieldfield_infofield_count
method_countu21
methodmethod_infomethod_count
attribute_countu21
atrributeattribute_infoattribute_count

可分为:无符号数+表,其中表也是由无符号数+其他表组合的,最终全部都是由无符号数组成的

Byte Ording (字节存储顺序)

在class是以8位一个字节为基础二进制流,紧凑排列。

Big-Endia (高位存放在低地址,低位存放在高地址) (注:TCP传输的字节顺序就采用大端方式,第一次接受到的就是高位字节)

Litte-Endia (高位存放在高地址,低位存放在低地址)

例如一个int类型(4个字节) 采用大端那么高位存放在前面,低位存放在后面

魔法数:

为了安全考虑,通过字节码前2个字节判断是不是为CAFEBABE 进而判断加载的就是类文件,不能通过文件名后缀来判断,因为后缀可以随意改动

版本号:

记录的是当前编译成字节码的jdk版本号,jvm支持向下兼容,1.8版本的jvm可以加载小于等于1.8版本编译的字节码。1.8对应于主版本52

常量池:常用的有14种常量类型,通过tag字段来区分

常量池主要存放字面常量和符号引用,字面常量;如final修饰的常量值、字符串

符号引用:类和接口的全限定名,字段名称和描述符,方法名称和描述符

UTF-8常量CONSTANT_Utf8_info:tag=1,采用utf-8缩略编码和普通编码区别: 如果在’\u0001’到’\u007f’之间的字符串(1-127的ASII码),从’\u0080’到’\u07ff’之间采用两个字节,从’\u0800’到’\uffff’之间采用三个字节

名称类型数量
tagu11
lengthu21
bytesu1length

CONSTANT_Integer_info

采用高位在前存储int值(Big-Endia 低位在高地址)

名称类型数量
tagu11
bytesu41

CONSTANT_Long_info

CONSTANT_Float_info

CONSTANT_Double_info

CONSTANT_Class_info

name_index为常量索引(第几个常量),指向的就是CONSTANT_Utf8_info的常量 (类或接口的全限定名)

名称类型数量
tagu11
name_indexu21

CONSTANT_String_info

name_index为常量索引(第几个常量),指向的就是CONSTANT_Utf8_info的常量 (字符串字面量索引)

名称类型数量
tagu11
name_indexu21

CONSTANT_Fieldref_info

存放的是那个类下的那个字段信息

name_index指向声明字段的类或接口CONSTANT_Class_info索引项

name_type_index 指向声明字段描述符CONSTANT_Name-AndType_info索引项

名称类型数量
tagu11
name_indexu21
name_type_indexu21

CONSTANT_Methodref_info

存放的是那个类下的方法信息,name_index 表示为class_info 类描述符索引项 name_type_index代表方法描述符索引项

名称类型数量
tagu11
name_indexu21
name_type_indexu21

CONSTANT_Interface-MethodRef_info

name_index 表示为方法接口的class_info 的索引项

名称类型数量
tagu11
name_indexu21
name_type_indexu21

CONSTANT_Name-AndType_info

name_index:指向该字段或方法名称常量项CONSTANT_Utf8_info索引

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

名称类型数量
tagu11
name_indexu21
descriptor_indexu21

访问标志

修饰类的访问标志,ACC_PUBLIC(0x0001) 是否位public类型, ACC_FINAL 是否声明为final只有类可以设置,

ACC_ABSTRACT(0x0400) 是否为抽象类型,只有接口或抽象类为真

如果该类对应的标志位为真,那么就对应于标志值,其他就为0

access_flags 为 0x0001|0x0400 那么该类的访问标志为 public abstract

类索引、父类索引、接口索引集合

类索引和父类索引都是由u2类型的数据,指向的都是 CONSTANT_Class_info的索引项,然后其索引值就可以找到CONSTANT_Utf8_info类型的常量中的全限定名,而接口索引集合是一组u2类型的数据的集合。

类索引用来确定这个类的全限定名

父类索引用来确定父类的全限定名,除了Object类其他类都会有一个父类

接口索引集合,对于入口第一项—u2类型表示为该类实现(implements)或继承(extends)接口的数量,后面就是指向class_info索引项集合

字段表集合

与类中的access_flags项目非常类似

name_index 指向字面常量字符串索引,表示字段名称

descriptor_index 指向字面量常量字符串索引,表示字段描述符号(字段类型)

名称类型数量
access_flagsu21
name_indexu21
descriptor_indexu21
attributes_countu21
attributesattribute_infoattributes_count

描述符规则:
B: byte

Z: boolean

V: void

L: 对象类型 Ljava/lang/Object

[: 数组类型 [[Ljava/lang/String <> String[][] [I <> int[]

private int a 在class 字节码存储的就是 access_flags为 0x0002(private)

name_index为0x0005代表第五项常量是一个CONSTANT_Utf8_info类型字符串 值为m

descriptor_index为0x0006 代表第六项常量,指向常量池字符串为I

attributes_count为0 表示没有额外的属性描述信息

如果改成 final static int a = 3 那么就会存在一项名称为ConstantValue 属性其值指向常量3

注意:

字段表集合中不会列举超类或继承的接口中的字段,但是有可能会列举原本在java代码不存在的字段,例如在内部类中,为了保持对外部类的访问性,会自动添加指向外部类实例的字段。

在java语言中,字段不能重载,不管数据类型或修饰符号是否不同,都必须使用不同的名称

在jvm字节码中,只要两个字段描述符不同,重名是合法的

方法表集合

和字段表结构相同,只不过acces_flags增加了 ACC_SYNCHRONIZED ACC_ABSTRACT 去掉了 transient 和 volatile 标志位

其中方法里面的字节码存放在属性表中的Code属性里面

名称类型数量
access_flagsu21
name_indexu21
descriptor_indexu21
attributes_countu21
attributesattribute_infoattributes_count

如果父类方法没有在子类重写,方法表集合中就不会出现来自父类的方法信息(但是会存在一个虚方法表,此表存放就是父类中所有方法和对应的入口地址,方便动态查找实际类型的方法(多态实现)),但是可能由编译器自动生成新的方法,例如 类构造器 和实例构造器 方法。

在java语言中,方法的重载,除了方法名相同外,要保证不同特征签名, 方法签名:方法的参数类型,位置和个数,返回值不会包含在方法签名。

在jvm字节码中,如果两个方法有相同名称和特征签名,但返回值不同,那么也可以合法共存于同个class文件中

属性表集合

Code属性表:

javac将源代码中的方法体代码编译成对应的字节码集存放到方法表中的Code属性表中,对于接口或抽象类中的抽象方法表就没有Code属性

max_stack 代表了操作数栈深度的最大值,例如 方法 A中调用方法B 然后方法B调用方法C那么 max_stack=3

max_local 代表局部变量所需存储空间,单位为Slot,对于byte short int float boolean char 和returnAddress每个局部变量占有一个Slot,而long double使用两个Slot存放

code_length和code用来存储java代码编译后生成的字节码指令, code_length代表字节码长度,code存放就是u1类型的字节码序列,一个u1类型取值范围0-255 表示256条指令,当虚拟机读取到字节码时,会到字节码指令对应关系表中 找到对应的指令。

code_length虽然为u4 能存放2的32次方-1个字节字节码,但是虚拟机限制了一个方法不能超过65535条字节码指令,否则javac编译不通过。

java程序代码信息分为代码(Code 方法体中java代码)和元数据(Metadata, 包括类、字段、方法定义以及其他信息 如常量池)两部分

有一篇对class 文件格式分析的很好的文章JVM字节码–Class文件格式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值