java类文件结构_JAVA Class类文件结构

前言

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

Class类文件结构

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

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

attribute_count

1

attribute_info

attributes

attributes_count

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

数据项

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

常量池

字段表

方法表

属性表

常量池

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

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

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

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

类和接口的全限定名

字段的名称和描述符

方法的名称和描述符

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

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

常量池中数据项类型

类型标志

类型描述

CONSTANT_Utf8

1

UTF-8编码的Unicode字符串

CONSTANT_Integer

3

int类型字面值

CONSTANT_Float

4

float类型字面值

CONSTANT_Long

5

long类型字面值

CONSTANT_Double

6

double类型字面值

CONSTANT_Class

7

对一个类或接口的符号引用

CONSTANT_String

8

String类型字面值

CONSTANT_Fieldref

9

对一个字段的符号引用

CONSTANT_Methodref

10

对一个类中声明的方法的符号引用

CONSTANT_InterfaceMethodref

11

对一个接口中声明的方法的符号引用

CONSTANT_NameAndType

12

对一个字段或方法的部分符号引用

访问标志

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

标志名称

标志值

含义

ACC_PUBLIC

0x00 01

是否为Public类型

ACC_FINAL

0x00 10

是否被声明为final,只有类可以设置

ACC_SUPER

0x00 20

是否允许使用invokespecial字节码指令的新语义.

ACC_INTERFACE

0x02 00

标志这是一个接口

ACC_ABSTRACT

0x04 00

是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假

ACC_SYNTHETIC

0x10 00

标志这个类并非由用户代码产生

ACC_ANNOTATION

0x20 00

标志这是一个注解

ACC_ENUM

0x40 00

标志这是一个枚举

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

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

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

字段表集合

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

public static final String num="13234";

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

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

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

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

字段表结构如下:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

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

标志名称

标志值

含义

ACC_PUBLIC

0x0001

字段是否为public

ACC_PRIVATE

0x0002

字段是否为private

ACC_PROTECTED

0x0004

字段是否为protected

ACC_STATIC

0x0008

字段是否为static

ACC_FINAL

0x0010

字段是否为final

ACC_VOLATILE

0x0040

字段是否为volatile

ACC_TRANSTENT

0x0080

字段是否为transient

ACC_SYNCHETIC

0x1000

字段是否为由编译器自动产生

ACC_ENUM

0x4000

字段是否为enum

name_index表示的是字段的简单名称,像上面的简单名称就是“num”,descriptor_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文件存储格式中对方法和字段的描述完全一致,方法表的字段结构和字段表一样,包括访问标志、名称索引、描述符索引、属性表集合几项。这些数据的含义非常类似,在访问标志和属性表集合有所区别。

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

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

标志名称

标志值

含义

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_SYHCHRONRIZED

0x00 20

方法是否为synchronized

ACC_BRIDGE

0x00 40

方法是否是有编译器产生的方法

ACC_VARARGS

0x00 80

方法是否接受参数

ACC_NATIVE

0x01 00

方法是否为native

ACC_ABSTRACT

0x04 00

方法是否为abstract

ACC_STRICTFP

0x08 00

方法是否为strictfp

ACC_SYNTHETIC

0x10 00

方法是否是有编译器自动产生的

方法里的代码

方法的定义可以通过访问标志、名称和描述符索引来表述清除,那么方法中的代码又在哪里呢?我们之前提到了属性表集合,方法里的java代码经过编译器编译成字节码指令后,存放在方法的属性表集合里一个名为“Code”的属性里。

属性表集合

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

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

java7中定义的属性如下表:

属性名称

使用位置

含义

Code

方法表

Java代码编译成的字节码指令

ConstantValue

字段表

final关键字定义的常量池

Deprecated

类,方法,字段表

被声明为deprecated的方法和字段

Exceptions

方法表

方法抛出的异常

EnclosingMethod

类文件

仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法

InnerClass

类文件

内部类列表

LineNumberTable

Code属性

Java源码的行号与字节码指令的对应关系

LocalVariableTable

Code属性

方法的局部便狼描述

StackMapTable

Code属性

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、付费专栏及课程。

余额充值