JAVA Class类文件结构

前言

要想深入的了解jvm,了解java编译后的类文件结构和字节码是很有必要的。虽然这部分内容(主要是class文件的数据结构)比较枯燥,但是这也是最基础的内容,是我们深入理解jvm的内存、类的加载等内容的基石。

Class类文件结构

class文件是一组以8位字节为基础的二进制流,各个数据项目按照顺序排列在Class文件中,中间没有任何分隔符。因此整个class文件中存储的内容几乎全是程序运行时的必要数据。当遇到需要占用8位以上字节空间的数据项时,会按照高位在前的方式分割成若干个8位字节存储。

class文件格式如下:

类型名称数量
u4magic1
u2minor_version1
u2major_version1
u2constant_pool_count1
cp_infoconstant_poolconstant_pool_count - 1
u2access_flags1
u2this_class1
u2super_class1
u2interfaces_count1
u2interfacesinterfaces_count
u2fields_count1
field_infofieldsfields_count
u2methods_count1
method_infomethodsmethods_count
u2attribute_count1
attribute_infoattributesattributes_count

class文件只有两种伪数据结构:无符号数和表。可以看到每个表的前面都会有一XX count的,这是一个前置容量计数器,用来记录对应类型的数量。

数据项

class文件的数据项很多,这里不展开一个个地讲,主要介绍一些关键的。

  1. 常量池
  2. 字段表
  3. 方法表
  4. 属性表

常量池

常量池相信很多人都听过,这里的常量池指的是class文件中的常量池。

常量池中主要存储两类类型:字面量和符号引用。

字面量:字面量指的是java语言层的常亮,如String s="123",那么这个123就是常量。对于基本类型的封装类型,范围在-127-128之间的,也是常量。当然了,声明为final的值,在整个程序运行过程中不可变,自然也是常量了。

符号引用java中的符号引用主要包括以下3类常量:

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

java是在虚拟机加载class文件的时候进行动态连接的,class文件中不会保存各方法字段的最终内存布局信息,因此这些字段、方法的符号引用不经过虚拟机运行时转换的话,无法得到真正的内存地址,也就无法被jvm使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建或运行时解析,翻译到具体的内存地址中。

常量池中的每一项常量都是一个表,这些表有一个共同的特点,每个表的第一位都是一个u1类型的标志位,取值如下:

常量池中数据项类型类型标志类型描述
CONSTANT_Utf81UTF-8编码的Unicode字符串
CONSTANT_Integer3int类型字面值
CONSTANT_Float4float类型字面值
CONSTANT_Long5long类型字面值
CONSTANT_Double6double类型字面值
CONSTANT_Class7对一个类或接口的符号引用
CONSTANT_String8String类型字面值
CONSTANT_Fieldref9对一个字段的符号引用
CONSTANT_Methodref10对一个类中声明的方法的符号引用
CONSTANT_InterfaceMethodref11对一个接口中声明的方法的符号引用
CONSTANT_NameAndType12对一个字段或方法的部分符号引用

访问标志

访问标志用来识别类或接口的访问信息,比如这个Class是类还是接口,是public还是private,是否被声明为final等。具体标志位及含义如下:

标志名称标志值含义
ACC_PUBLIC0x00 01是否为Public类型
ACC_FINAL0x00 10是否被声明为final,只有类可以设置
ACC_SUPER0x00 20是否允许使用invokespecial字节码指令的新语义.
ACC_INTERFACE0x02 00标志这是一个接口
ACC_ABSTRACT0x04 00是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC0x10 00标志这个类并非由用户代码产生
ACC_ANNOTATION0x20 00标志这是一个注解
ACC_ENUM0x40 00标志这是一个枚举

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

我们都知道,java中是单继承多实现的,除了Object类之外每个类都有父类,因此它们是唯一的,而一个类可以实现多个接口,因此接口是不唯一的,用集合表示。类索引和父类索引都是用一个u2类型数据来表示的,而接口索引集合则是一组u2类型的数据表示。

类索引、父类索引和接口索引集合都按顺序排在访问标志的后面。类索引和父类索引u2类型的索引各指向一个类型为CONSTANT_Class_info的类描述符常量,用来描述具体的类。对于接口索引第一项u2则为接口索引计数器,用来记录实现了多少个接口,如果为0则后面不再占用任何字节。

字段表集合

字段表用于声明类或接口中声明的变量。字段包括类变量和实例变量。在java中一个字段是如何描述的,举个的例子

public static final  String num="13234";

可以看出来,首先是访问范围,是公有的还是私有的,或者受保护的,这个信息决定了字段是否堆特定范围的类可见。

其次是一些关键字修饰的描述信息,是实例变量还是类变量,是否可变,并发可见性,是否可被序列化等,这些关键字包括staticfinalvolatiletransient等。

在后面便是字段的数据类型(基本数据类型、数组、对象)和名称。

上述的这些修饰符都是用布尔值来描述的,而数据类型和名称都是不确定的,通常引用常量池的常量来描述。

字段表结构如下:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

字段修饰符在access_flag下,access_flag的内容如下:

标志名称标志值含义
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

name_index表示的是字段的简单名称,像上面的简单名称就是numdescriptor_index 表示字段和方法的描述符。

描述符号的作用是用来描述字段的数据类型、方法的参数列表(数量、类型及顺序)和返回值。根据描述符的规则:基本数据类型以及代表无返回值的void类型都用一个大写的字符来表示,而对象类型则用字符L加对象的全限定名来描述,详情如下:

标志符含义
B基本数据类型byte
C基本数据类型char
D基本数据类型double
F基本数据类型float
I基本数据类型int
J基本数据类型long
S基本数据类型short
Z基本数据类型boolean
V基本数据类型void
L对象类型

对于数组类型,每一个维度用一个前置的“[”字符来描述,如定义个int[][]类型的二维数组,记录为:”[[I”。

用描述符来描述方法时,按照先参数列表后返回值的顺序描述。参数裂变按照参数顺序放在“()”内,如方法void login()描述符为()V,方法java.lang.String toString()的描述符为“()Ljava.lang.String”。

方法表集合

Class文件存储格式中对方法和字段的描述完全一致,方法表的字段结构和字段表一样,包括访问标志、名称索引、描述符索引、属性表集合几项。这些数据的含义非常类似,在访问标志和属性表集合有所区别。

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

voliatetransient不能修饰方法,所以访问标志没有了ACC_VOLATILE标志和ACC_TRANSIENT标志。同样,一些方法的关键字如:synchronizednativestrictfpabstract等可以修饰方法,其标志位取值如下:

标志名称标志值含义
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的属性里。

属性表集合

我们从上面不止一次的看到了属性表集合,不管是Class文件、字段表还是方法表中,都有属性表集合(attribute_info)这一项,用于描述某些特定场景的专有信息。

class文件其它数据项目严格要求顺序长度不同,属性表集合限制相对比较宽松,不要求各个属性表具有严格顺序,只要不与已有属性名重复,任何人实现的编译器均可向属性表写入自己的属性,jvm运行时会自动忽略掉不认识的属性。

java7中定义的属性如下表:

属性名称使用位置含义
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指令引用的引导方式限定符
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值