JVM:字节码文件

数据类型

字节码文件是一组以字节为单位的二进制流,各个数据项严格按顺序排列在文件中,中间没有分隔符,当遇见需要占用8字节以上空间的数据项时,会按照高位在前的分割方式分割成若干个8个字节进行排序,字节码文件有两种数据类型:

  • 无符号数:无符号数属于基本的数据类型,以u1、u2、u4和u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
  • 表:表是由多个无符号数或者其它表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以_info结尾。表是用于描述有层次关系的复合结构,整个字节码文件本质上也可以视作是一张表。

文件组成

My Very Cute Abinal Turns Savage In Full Moon Areas.

类型名称数量说明
u4magic1魔数,用于标识字节码文件
u2minor_version1字节码文件副版本
u2major_version1字节码文件主版本
u2constant_pool_count1常量池数量
cp_infoconstant_poolconstant_pool_count-1常量池
u2access_flag1访问标志
u2this_class1类索引
u2super_class1父类索引
u2interfaces_count1接口索引表数量
u2interfacesinterfaces_count接口索引表
u2fields-count1字段表数量
field_infofieldsfields-count字段表
u2methods_count1方法表数量
methods_infomethodsmethods_count方法表
u2attributes_count1属性表数量
attribute_infoattributesattributes_count属性表

magic

字节码文件的前四个字节称为魔数(0xcafebabe),它唯一的作用是确定这个文件是否是一个可以被虚拟机接受的字节码文件。

minor_version和major_sersion

主版本副版本编译器版本
4531.1
4601.2
4701.3
4801.4
4901.5
5001.6
5101.7
5201.8
5301.9
5301.10
5501.11

constant_pool

由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。这个容量计数是从1而不是0开始的,这样做的目的在于如果后面某些指向常量池的索引值的数据在特定情况下需要表达不引用任何一个常量池项的含义,可以把索引值设置为0来表示。常量池中每一项都是一个表,表中的内容将在类加载后放入元空间的运行时常量池中(字符串常量池在堆中)。

常量池项

每一个常量池项的第一个字节表示该项的类型(tag),接下来的几个字节表示常量池项的具体内容:

类型tag值
CONSTANT_Integer_info3
CONSTANT_Float_info4
CONSTANT_String_info8
CONSTANT_MethodHandle_info15
CONSTANT_MethodType_info16
CONSTANT_Dynamic_info
CONSTANT_Utf8_info

用于存储字符串的内容,

CONSTANT_Utf8_info{
	u1 tag;//固定值1
	u2 length;//bytes数组的长度
	u1 bytes[length];
}
CONSTANT_Long_info和CONSTANT_Double_info

用于表示long和double类型的常量。

CONSTANT_Long_info{
	u1 tag;//固定值5
	u4 high_bytes;
	u4 low_bytes;
}

CONSTANT_Double_info{
	u1 tag;//固定值6
	u4 high_bytes;
	u4 low_bytes;
}
CONSTANT_Class_info

用于表示类或接口。

CONSTANT_Class_info{
	u1 tag;//固定值7
	u2 name_index;//指向CONSTANT_Utf8_info的常量,它存储的是类或接口的全限定名
}
CONSTANT_NameAndType_info

用于表示字段或方法。

CONSTANT_Class_info{
	u1 tag;//固定值12
	u2 name_index;//指向CONSTANT_Utf8_info的常量,它存储的是字段或方法的名字
	u2 descriptor_index;//指向CONSTANT_Utf8_info的常量,它存储的是字段或方法的描述符
}
CONSTANT_Fieldref_info、CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info

用于描述字段、方法和接口方法。

CONSTANT_Fieldref_info{
	u1 tag;//固定值9
	u2 class_index;//指向CONSTANT_Utf8_info的常量,它存储的是字段所在类的类信息
	u2 name_and_type_index;//指向CONSTANT_Utf8_info的常量,它存储的是字段的名称和描述符
}

CONSTANT_Methodref_info{
	u1 tag;//固定值10
	u2 class_index;//指向CONSTANT_Utf8_info的常量,它存储的是方法所在类的类信息
	u2 name_and_type_index;
}

CONSTANT_InterfaceMethodref_info{
	u1 tag;//固定值11
	u2 class_index;//指向CONSTANT_Utf8_info的常量,它存储的是方法所在的接口信息
	u2 name_and_type_index;
}
CONSTANT_MethodType_info、CONSTANT_MethodHandle_info和CONSTANT_InvoleDynamic_info

用于更好的支持动态语言的调用。InvoleDynamic用于为InvoleDynamic指令提供启动引导方法。

CONSTANT_InvoleDynamic_info{
	u1 tag;//固定值18
	u2 bootstrap_method_attr_index;//指向引导方法表bootstrap_methods[]数组的索引
	u2 name_and_type_index;//指向CONSTANT_NameAndType_info的索引
}

存储内容

字符串常量池主要存放两大常量:字面量和符号引用。字面量包括文本字符串和声明为final的常量等,符号引用包括类和接口的全限定名(将全类名中的点改成斜杠即为全限定名,可以在多个全限定类名之间使用分号分隔)、字段的名称和描述符、方法的名称和描述符和动态调用点和动态常量等。

描述符

标识符含义
Bbyte
Cchar
Ddouble
Ffloat
Iint
Jlong
Sshort
Zboolean
Vvoid
LL后跟对象的全限定名
[[后跟数组类型的全限定名
()前参数列表,后返回值的方法

access_flags

用于识别一个类或接口的访问信息。

标志名称标志值含义
ACC_PUBLIC0x0001是否为public
ACC_FINAL0x0010是否为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语义,jdk2以后的这个值都为true
ACC _INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为abstract,对于接口或者抽象类来说,此标志值为真,其它类型值为假
ACC_SYNHETIC0x1000标识这个类并非由用户代码产生的
ACC_ANNOTATION0x2000标识这是一个注解类
ACC_ENUM0x4000标识这是一个枚举类

this_class、super_class和interfaces

this_class指向CONSTANT_Utf8_info的常量,它存储的是类或接口的名字,super_class指向CONSTANT_Utf8_info的常量,它存储的是直接父类的名字。interfaces表示类或者接口的直接父接口,接口顺序从左到右排列。

fields

字段表内包括类字段和实例字段的所有信息,但不包含从父类或者父接口中继承而来的字段,但有可能出现原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,编译器就会自动添加指向外部类实例的字段。另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码文件来讲,只要两个字段的描述符不是完全相同,那字段重名也是合法的。

fields{
	u2 fields_count;
	field_info fields[fields_count];
}

每个总段field_info的格式如下:

field_info{
	u2 access_flags;//字段访问标识
	u2 name_index;//字段名称索引
	u2 descriptor_index;//字段描述符索引
	u2 attributes_count;//属性表数量
	attribute_info attributes[attributes_count];//一个字段可能拥有一些属性,用于存储额外信息,比如初始化值等
}

descriptor_index

标志名称标志值含义
ACC_PUBLIC0x0001字段是否public
ACC_PRIVATE0x0002字段是否private
ACC_PROTECTED0x0004字段是否protected
ACC_STATIC0x0008字段是否static
ACC_FINAL0x0010字段是否final
ACC_VOLATILE0x0040字段是否volatile
ACC_TRANSIENT0x0080字段是否为transient
ACC_SYNTHETIC0x1000字段是否由编译器自动产生
ACC_ENUM0x4000字段是否为枚举类型的变量

methods

方法表包含实例方法和静态方法的全部信息,但同样地,有可能会出现由编译器自动添加的方法,最常见的便是类构造器<clinit>()方法和实例构造器<init>()方法,在java中不允许在一个类或接口中声明多个签名相同的方法,但是在字节码中可以存放多个签名相同的方法,唯一的条件是这些人方法之间的返回值不能相同。

类型名称数量说明
u2access_flags1访问标识
u2name_index1名称索引
u2descriptor_index1描述符索引
u2attributes_count1属性表数量
attribute_infoattributesattributes_count属性表
access_flags
标志名称标志值含义
ACC_PUBLIC0x0001方法是否public
ACC_PRIVATE0x0002方法是否private
ACC_PROTECTED0x0004方法是否protected
ACC_STATIC0x0008方法是否static
ACC_FINAL0x0010方法是否final
ACC_SYNCHRONIZED0x0020方法是否为synchronized
ACC_BRIDGE0x0040方法是否由编译器产生的桥接方法
ACC_VARARGS0x0080方法是否接受不定参数
ACC_NATIVE0x0100方法是否为native
ACC_ABSTRACT0x0400方法是否为abstract
ACC_STRICT0x0800方法是否为strictfp
ACC_SYNTHETIC0x1000是否由编译器自动产生

attributes

字节码文件、字段表和方法表都可以携带自己的属性表以描述某些场景专有的信息,属性表的限制比较宽松,不再要求各个属性有严格的顺序,只要属性不重名就允许编译器向属性表中写入自己定义的属性信息,JVM在运行时会忽略掉它不认识的属性,现属性表中已预定义的属性有29项。

属性名使用位置含义
Code方法表Java 代码编译成的字节码指令
ConstantValue字段表由final关键字定义的常量值
Deprecated字节码文件、方法表、字段表被声明为deprecated的方法和字段
Exceptions方法表方法抛出的异常列表
EnclosingMethod字节码文件仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标示这个类所在的外围方法
InnerClasses字节码文件内部类列表
LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量插述
StackMapTableCode属性JDK6中新增的属性、供新的类型检查验证器检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
Signature字节码文件、方法表、字段表JDK5中新增的属性,用于支持范型情况下的方法签名。在Java语言中、任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,则Signature属性会为它记录泛型签名信息。由于Java的范型采用擦除法实现、为了避免类型信息被擦除后导致签名混乱、需要这个属性记录范型中的相关信息
SourccFile字节码文件记录源文件名称
SourceDcbugExtension字节码文件JDK5中新增的属性,用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR45提案为这些非Java语言编写,却需要编译成字节码并运行在Java虚拟机中的程序提供了一个进行调试的标准机制.使用该属性就可以用于存储这个标准所新加入的调试信息
Synthetic字节码文件、方法表、字段表标识方法或字段为编译器自动生成的
LocalVariableTypeTable字节码文件JDK5中新增的属性,它使用特征签名代替描述符、是为了引入泛型语法之后能播述泛型参数化类型面添加
RuntimeVisibleAnnotations字节码文件、方法表、字段表JDK5中新增的属性,为动态注解提供支持。该属性用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的
RuntimeInvisibleAnnotations字节码文件、方法表、字段表JDK 5中新增的属性,与 RuntimeVisibleAnnotations属性作用刚好相反,用于指明哪些注解是运行时不可见的
Runtime VisibleParameterAnnotations方法表JDK5中新增的属性,作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法参数
RuntimelnvisibleParameterAnnotations方法表JDK5中新增的属性,作用与RuntimeInvisibleAnnotations属性类似。只不过作用对象为方法参数
AnnotationDefault方法表JDK5中新增的属性,用于记录注解类元素的默认值
BootstrapMcthods字节码文件JDK 7中新增的属性,用于保存invokedynamic指令引用的引导方法限定符
RuntimeVisibleTypeAnnotations类、方法表、字段表,Code属性JDK8中新增的属性,为实现JSR 308中新增的类型注解提供的支持.用于指明哪些类注解是运行时(实际上运行时就是进行反射调用)可见的
RuntimelnvisibleType Annotations字节码文件、方法表、字段表,Code属性JDK 8中新增的属性,为实现JSR 308中新增的类型注解提供的支持、与RuntimeVisibleTypeAnnotations属性作用刚好相反、用于指明哪些注解是运行时不可见的
MethodParameters方法表JDK 8中新增的属性,用于支持(编译时加上-parameters参数)将方法名称编译进Class文件中.并可运行时获取。此前要获取方法名称(典型的如IDE的代码提示)只能通过JavaDoc中得到
Module字节码文件JDK9中新增的属性,用于记录一个Module的名称以及相关信息( requires . exports、opens . uses ,provides )
ModulePackages字节码文件JDK9中新增的属性,用于记录一个模块中所有被exports或者opens的包
ModuleMainClass字节码文件JDK 9中新增的属性、用于指定一个模块的主类
NestHost字节码文件JDK11中新增的属性,用于支持嵌套类(Java中的内部类)的反射和访问控制的API.一个内部类通过该属性得知自己的宿主类
NestMembers字节码文件JDK11中新增的属性,用于支持嵌套类(Java中的内部类)的反射和访问控制的API、一个宿主类通过该属性得用自已有界些内部类

code

该属性位于方法表的属性表中,它用于存放方法体中代码经编译后生成的字节码。

类型名称数量说明
u2attribute_name_index1属性名索引
u4attribute_length1属性值长度
u2max_stack1操作数栈最大深度
u2max_locals1局部变量表所需要的变量槽
u4code_length1字节码长度
u1codecode_length字节码指令,顾名思义每个指令就是一个u1类型的单字节,当虚拟机读取到code中的一个字节码时,就可以对应找出这个字节码代表的是什么指令,并且可以知道这条指令后面是否需要跟随参数,以及后续的参数应当如何解析。
u2exception_table_length1异常表长度
exception_infoexception_tableexception_table_length异常表
u2attributes_count1属性表长度
attribute_infoattributesattributes_count属性表

exception_info

类型名称数量说明
u2start_pc1字节码起始行号
u2end_pc1字节码结束行号
u2handler_pc1处理异常的字节码行号
u2catch_type1如果字节码在start_pc和end_pc结束行之间出现了类型catch_type的异常,则转到第handler_pc行继续处理。当catch_type的值为0时,代表任意异常情况都需要转到handler_pc处进行处理。

Exception

该属性位于方法表的属性表中,它用于列举出方法可能抛出的受查异常。

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2number_of_exceptions1
u2exception_index_tablenumber_of_exceptions

LineNumberTable

该属性位于字节码文件的属性表中,它用于描述Java源码行号与字节码行号之间的对应关系。如果选择不生成该属性,对程序运行产生的最主要影响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2line_number_table_length1
line_number _infoline_number_tableline_number_table_length
line_number _info
类型名称数量
u2start_pc1
u2line_number1

LocalVariableTable

该属性位于字节码文件的属性表中,它用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,如果没有生成这项属性,最大的影响就是当其它人引用这个方法时,所有的参数名称都将会丢失,譬如IDE将会使用诸如arg0、arg1之类的占位符代替原有的参数名。

类型名称说明
u2attribute_name_index1
u4attribute_length1
u2local_variable_table_length1
local_variable_infolocal_variable_tablelocal_variable_table_length
local_variable_info
类型名称数量
u2start_pc1
u2length1
u2name_index1
u2descriptor_index1
u2index1

LocalVariableTypeTable

该属性位于字节码文件的属性表中,它的结构与LocalVariableTable非常相似,仅仅是把记录的字段描述符的descriptor_index替换成了字段的特征签signature。对于非泛型类型来说,描述符和特征签名能描述的信息是能吻合一致的,但是泛型引入之后,由于描述符中泛型的参数化类型被擦除掉,描述符就不能准确描述泛型类型了。因此出现了该属性,它使用字段的特征签名来完成泛型的描述。

SourceFile

该属性位于字节码文件的属性表中,它用于记录这个字节码文件的源文件名称,如果没有这项属性,在抛出异常时堆栈中将不会显示错误代码所属的文件名。

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2sourcefile indexl

ConstantValue

该属性位于字段表的属性表中,只有静态变量才可以使用这项属性。它用于通知虚拟机自动为静态变量赋值。对于类变量,有两种方式可以赋值:在类构造器<clinit>()方法中或者使用该属性。目前Oracle公司实现的Javac编译器的选择是:如果同时使用finalstatic来修饰一个变量,并且这个变量是基本类型或字符串常量,就会生成该属性来进行初始化;如果这个变量没有被final修饰,或者并非基本类型和字符串常量,则在<clinit>()方法中进行初始化。

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2constantvalue index1

InnerClasses

该属性位于字节码文件的属性表中,它用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,编译器就会为它以及它所包含的内部类生成该属性。
类型|名称|数量
–|–|–|–
u2|attribute_name_index|1
u4|attribute_length|1
u2|number_of_classes|1|
inner classes info|inner classes|number of classes

inner_classes_info

类型名称数量
u2inner_class_info_indexl
u2outer_class_info_index1
u2inner_name_index1
u2inner_class_access_flags1

Deprecated和Synthetic

Deprecated属性用于表示某个类、字段或者方法,已经被程序作者定为不再推荐使用。Synthetic属性代表此字段或者方法并不是由Java源码直接产生的,而是由编译器自行添加的.其中attribute_length数据项的值必须为零,因为没有任何属性值需要设置。

类型名称数量
u2attribute_name_index1
u4attribute_length1

Signature

任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,则该属性会为它记录泛型签名信息。

类型名称数量说明
u2attribute_name_index1
u4attribute_length1
u2signature_index1类签名或方法类型签名或字段类型签名

BootstrapMethods

该属性位于字节码文件的属性表中,如果一个字节码文件的常量池中出现过一次或多次CONSTANT_InvokeDynamic_info类型的常量,那么它的属性表中必须有且只有一个该属性。它用于保存invokedynamic指令引用的引导方法限定符。

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2num_bootstrap_methods1
bootstrap_methodbootstrap_methodsnum_bootstrap methods

bootstarp_method

类型名称数量
u2bootstrap method_ref1
u2num_bootstrap_arguments1
u2bootstrap argumentsnum_bootstrap arguments

MethodParameters

该属性位于方法表的属性表中,它用于记录方法的各个形参名称和信息。

类型名称数量
u2attribute_name_index1
u4attribute_length1
ulparameters_count1
parameterparametersparameters_count

parameter

类型名称数量
u2name_index1
u2access_flags1
access_flags是参数的状态指示器,它可以包含以下三种状态中的一种或多种:
  • 0x0010(ACC_FINAL):表示该参数被final修饰。
  • 0x1000(ACC_SYNTHETIC):表示该参数并未出现在源文件中,是编译器自动生成的。
  • 0x8000(ACC_MANDATED):表示该参数是在源文件中隐式定义的。Java语言中的典型场景是this关键字。

RuntimeXxxAnnotations

它们记录了类、字段或方法的声明上记录运行时可见注解,当我们使用反射API来获取类、字段或方法上的注解时,返回值就是通过这个属性来取到的。

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2num _annotations1
annotationannotationsnum annotations
annotation
类型名称数量
u2type_index1
u2num_element_value_pairs1
element_value_pairelement_value_pairsnum_element_ value_pairs
element_value_pairs中每个元素都是一个键值对,代表该注解的参数和值。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亻乍屯页女子白勺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值