JVM Class文件结构2

本文深入剖析了Java Class文件的结构,包括访问标志、类索引、接口索引、字段表、方法表和属性表等核心组成部分。访问标志如ACC_PUBLIC、ACC_FINAL等用于标识类或方法的访问权限和特性。字段表和方法表描述了类中的变量和方法,而属性表则包含额外信息,如源文件名称、注解等。通过对Class文件的理解,有助于更好地掌握Java虚拟机的工作原理。

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

访问标志

标志名称标志值含义
ACC_PUBLIC0x0001标志为public类型
ACC_FINAL0x0010标志被声明为final,只有类可以设置
ACC_SUPER0x0020标志允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真。(使用增强的方法调用父类方法)
ACC_INTERFACE0x0200标志这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC0x1000标志此类并非由用户代码产生(即:由编译器产生的类,没有源码对应)
ACC_ANNOTATION0x2000标志这是一个注解
ACC_ENUM0x4000标志这是一个枚举
  • 在常量池后,紧跟着访问标记

  • 使用两个字节表示

  • 用于识别一些类或者接口层次的访问信息:

    • Class是类还是接口
    • 是否定义为public类型
    • 是否定义为abstract类型
    • 如果是类,是否被声明为final等
  • 类的访问权限通常为ACC_开头的常量

  • 每一种类型的表示都是通过设置访问标记的32位中的特定位来实现的

  • 若是public、final的类,则该标记为ACC_PUBLIC、ACC_FINAL。

  • 使用ACC_SUPER可以让类更准确地定位到父类的方法super.method(),现代编译器都会设置并且使用这个标记。

补充

  • 带有ACC_INTERFACE标志的class文件表示的是接口而不是类,反之则表示的是类而不是接口。
  • 如果一个class文件被设置了ACC_INTERFACE标志,那么同时也得设置ACC_ABSTRACT标志,同时它不能再设置ACC_FINALACC_SUPERACC_ENUM标志。
    如果没有设置ACC_INTERFACE标志,那么这个class文件可以具有上表中除ACC_ANNOTATION外的其他所有标志。当然,ACC_FINALACC_ABSTRACT这类互斥的标志除外。这两个标志不得同时设置。
  • ACC_SUPER标志用于确定类或接口里面的invokespecial指令使用的是哪一种执行语义。针对Java虚拟机指令集的编译器都应当设置这个标志
  • Java SE 8及后续版本来说,无论class文件中这个标志的实际值是什么,也不管class文件的版本号是多少,Java虚拟机都认为每个class文件均设置了ACC_SUPER标志。
  • ACC_SUPER标志是为了向后兼容由旧Java编译器所编译的代码而设计的,目前的ACC_SUPER标志在由JDK1.0.2之前的编译器所生成的access_flags中是没有确定含义的,如果设置了该标志,那么Oracle的Java虚拟机实现会将其忽略
  • ACC_SYNTHETIC标志意味着该类或接口是由编译器生成的,而不是由源代码生成的
  • 注解类型必须设置ACC_ANNOTATION标志。如果设置了ACC_ANNOTATION标志,那么也必须设置ACC_INTERFACE标志
  • ACC_ENUM标志表明该类或其父类为枚举类型

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

长度含义
u2this_class
u2super_class
u2interfaces_count
u2interfaces[interfaces_count]
  • 三项数据来确定这个类的继承关系
  • 类索引用于确定这个类的全限定名
  • 父类索引用于确定这个类的父类的全限定名,Java语言不允许多重继承,所以父类索引只有一个
  • 除了java.lang.Object之外,所有的Java类都有父类,因此除Ijava.lang.Object外,所有Java类的父类索引都不为0
  • 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(这个类是一个接口则是extends语句)后的接口顺序排列在接口索引集合中

this_class

  • 2字节无符号整数,指向常量池的索引,提供了类的全限定名,如java/lang/Object,this_class的值必须是对常量池表中某项的一个有效索引值
  • 常量池在这个索引处的成员必须为CONSTANT_Class_info类型结构体,该结构体表示这个class文件所定义的类或接口

super_class

  • 2字节无符号整数,指向常量池的索引,提供了当前类的父类的全限定名,没有继承任何类,其默认继承的是java/lang/object类。
  • super_class指向的父类不能是final

interfaces

  • interfaces_count:接口计数器

  • 指向常量池索引集合,它提供了一个符号引用到所有已实现的接口

  • 一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class(当然这里必须是接口,不是类)

  • interfaces[]中每个成员的值必须是对常量池表中某项的有效索引值,它的长度为interfaces_count

  • 每个成员interfaces[i]必须为CONSTANT_Class_info结构

  • 在interfaces[]中,各成员所表示的接口顺序和对应的源代码中给定的接口顺序

字段表集合

  • 描述接口或类中声明的变量,字段(field)包括类级变量、实例级变量,不包括方法内部、代码块内部声明的局部变量

  • 字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。

  • 指向常量池索引集合,它描述了每个字段的完整信息,比如字段的标识符、访问修饰符(public、private或protected)

  • 是类变量还是实例变量(static修饰符)

  • 是否是常量(final修饰符)等


  • fields_count:字段计数器

  • fields_count的值表示当前class文件fields表的成员个数。使用两个字节来表示

注意事项

  • 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段
  • 在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。

字段

标志名称标志值含义数量
u2access_flags访问标志1
u2name_index字段名索引1
u2descriptor_index描述符索引1
u2attributes_count属性计数器1
attribute_infoattributes属性集合attributes_count
  • fields表中的每个成员都必须是一个fields_ info结构的数据项,用于表示当前类或接口中某个字段的完整描述

访问标识

一个字段的信息包括如下这些信息。这些信息中,各个修饰符是布尔值

标志名称标志值含义
ACC_PUBLIC0x0001字段是否为public
ACC_PRIVATE0x0002字段是否为private
ACC_PROTECTED0x0004字段是否为protected
ACC_STATIC0x0008字段是否为static
ACC_FINAL0x0010字段是否为final
ACC_VOLATILE0x0040字段是否为volatile(并发可见性)
ACC_TRANSTENT0x0080字段是否为transient(序列化)
ACC_SYNCHETIC0x1000字段是否为由编译器自动产生
ACC_ENUM0x4000字段是否为enum

描述符

  • 用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte,char,double,float,int,long,short,boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示

属性

  • 一个字段还可能拥有一些属性,用于存储更多的额外信息,比如初始化值、一些注释信息等
  • 属性个数存放在attribute_count中,属性具体内容存放在attributes数组中
  • 对于常量属性而言,attribute_length值恒为2。

方法表集合

  • methods:指向常量池索引集合,它完整描述了每个方法的签名

  • 字节码文件中,每一个method_info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public、private或protected),方法的返回值类型以及方法的参数信息等

  • 如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来

  • methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法

  • methods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息,比如:类(接口)初始化方法<clinit>()和实例初始化方法<init>())。

注意事项

  • Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载

  • 但在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个class文件中。

  • 尽管Java语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,但是和Java语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。

  • methods_count:方法计数器,两个字节来表示

  • methods_count的值表示当前class文件methods表的成员个数

  • methods表中每个成员都是一个method_info结构


  • methods表中的每个成员都必须是一个method_info结构,用于表示当前类或接口中某个方法的完整描述

  • 如果某个method_info结构的access_flags项既没有设置ACC_NATIVE标志也没有设置ACC_ABSTRACT标志,那么该结构中也应包含实现这个方法所用的Java虚拟机指令。

  • method_info结构可以表示类和接口中定义的所有方法,包括

    • 实例方法
    • 类方法
    • 实例初始化方法
    • 类或接口初始化方法

方法表结构

标志名称标志值含义数量
u2access_flags访问标志1
u2name_index方法名索引1
u2descriptor_index描述符索引1
u2attributes_count属性计数器1
attribute_infoattributes属性集合attributes_count

访问标志

标志名称标志值含义
ACC_PUBLIC0x0001public,方法可以从包外访问
ACC_PRIVATE0x0002private,方法只能本类访问
ACC_PROTECTED0x0004protected,方法在自身和子类可以访问
ACC_STATIC0x0008static,静态方法

属性表集合

  • 方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该class文件的源文件的名称。以及任何带有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的注解

  • 这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试

  • 字段表、方法表都可以有自己的属性表,用于描述某些场景专有的信息

  • 属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性

  • attributes_count:当前class文件属性表的成员个数

  • 属性表中每一项都是一个attribute_info结构

属性

  • 属性表的每个项的值必须是attribute_info结构
  • 属性表的结构比较灵活,各种不同的属性只要满足以下结构即可

通用格式

类型名称数量含义
u2attribute_name_index1属性名索引
u4attribute_length1属性长度
u1infoattribute_length属性表

属性类型

属性名称使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表final关键字定义的常量值
Deprecated类,方法,字段表被声明为deprecated的方法和字段
Exceptions方法表方法抛出的异常
EnclosingMethod类文件仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass类文件内部类列表
LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量描述
StackMapTableCode属性JDK1.6中新增的属性,供新的类型检查检验器和处理目标方法的局部变量和操作数有所需要的类是否匹配
Signature类,方法表,字段表用于支持泛型情况下的方法签名
SourceFile类文件记录源文件名称
SourceDebugExtension类文件用于存储额外的调试信息
Synthetic类,方法表,字段表标志方法或字段为编译器自动生成的
LocalVariableTypeTable是哟很难过特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations类,方法表,字段表为动态注解提供支持
RuntimeInvisibleAnnotations类,方法表,字段表用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotation方法表作用与RuntimeVisibleAnnotations属性类似,只不过作用对象或方法
RuntimeInvisibleParameterAnnotation方法表作用与RuntimeInvisibleAnnotations属性类似,只不过作用对象或方法
AnnotationDefault方法表用于记录注解类元素的默认值
BootstrapMethods类文件用于保存invokeddynamic指令引用的引导方法限定符

Code

类型名称数量含义
u2attribute_name_index1属性名索引
u4attribute_length1属性长度
u2max_stack1操作数栈深度的最大值
u2max_locals1局部变量表所需的存续空间
u4code_length1字节码指令的长度
u1codecode_lenth存储字节码指令
u2exception_table_length1异常表长度
exception_infoexception_tableexception_length异常表
u2attributes_count1属性集合计数器
attribute_infoattributesattributes_count属性集合

LineNumberTable

LineNumberTable_attribute {
	u2 attribute_name_index;
	u4 attribute_length;
	u2 line_number_table_length;
	
	{	
		u2 start_pc;
	 	u2 line_number; 
	} line_number_table[line_number_table_length];
}
  • LineNumberTable属性是可选变长属性,位于Code结构的属性表

  • 来描述Java源码行号与字节码行号之间的对应关系。这个属性可以用来在调试的时候定位代码执行的行数

  • start_pc:即字节码行号

  • line_number:Java源代码行号

  • 在Code属性的属性表中,LineNumberTable属性可以按照任意顺序出现,

  • 多个LineNumberTable属性可以共同表示一个行号在源文件中表示的内容,即LineNumberTable属性不需要与源文件的行一一对应

LocalVariableTable

LocalVariableTable_attribute{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    
    {
        u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}
  • 可选变长属性,位于Code属性的属性表中。它被调试器用于确定方法在执行过程中局部变量的信息

  • 在Code属性的属性表中,LocalVariableTable属性可以按照任意顺序出现,Code属性中的每个局部变量最多只能有一个LocalVariableTable属性

  • start pc + length表示这个变量在字节码中的生命周期起始和结束的偏移位置(this生命周期从头到结尾,0~10)

  • index:这个变量在局部变量表中的槽位(槽位可复用)

  • name:变量名

  • descriptor:局部变量类型描述

文件结构总结

  • 随着Java平台的不断发展,在将来,Class文件的内容也一定会做进一步的扩充,但是其基本的格式和结构不会做重大调整
  • 从Java虚拟机的角度看,通过Class文件可以让更多的计算机语言支持Java虚拟机平台,因此,Class文件结构不仅仅是Java虚拟机的执行入口,更是Java生态圈的基础和核心
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值