字节码初探
java文件在经过编译器编译后会生成.class文件,那么这个类型的文件内容或者格式是什么样子的呢?
注意,任何一个class文件都存着对应的唯一一个类或接口定义的信息,但是类和接口并不一定定义在class文件中,例如可以动态生成直接进入类加载器。
每个类都会生成一个class文件,即使一个java文件有多个类也会为每一个类都生成一个class文件
class文件中的数据类型
class文件中的数据没有分隔符,采用大端存储,所以对每一个数据都要严格排序
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
-
首先是一个class文件的标准开头0XCAFEBABE(魔数),不是该字符串起始的class文件jvm都不编译;
-
次版本号和主版本号,可以向下兼容编译,即高版本可以编译低版本的class文件;
-
常量池:(JDK13前)
每一个常量都是一个表来描述,目前一共有17个不同的常量类型,主要包括:
-
被模块导出的或者开放的包(package)
-
类和接口的全限定名
-
字段的名称和描述符
-
方法的名称和描述符
-
方法的句柄和方法类型
-
动态调用点和动态常量(不懂,待补充)
常量池中的所有表项:
eg:CONSTANT_Methodref_info这个表中就包括
-
tag-对应该表的类别,用以以后根据类别确定表里的信息
-
index-指向对应的CONSTANT_class_info表,即该方法属于哪一个类(CONSTANT_class_info储存一个tag与一个指向类的全限定名的index)
-
index-指向对应的CONSTANT_NameAndType_info表类型(内有tag,指向字段或方法的名称索引,指向字段或方法描述符的索引。
其他表或嵌套表或自成一派,皆以此类推。
此处要注意,java没有在编译的时候进行链接这一步,只会在虚拟机加载class文件的时候进行动态链接,也就是说,class文件并不会保存各个方法、字段在内存中的布局信息,不经过编译器运行时的转换无法获得真正的内存入口地址。(此处涉及到类加载机制,待补充到下一篇文章)。
-
访问标志
用于识别一些类或接口的访问信息,比如public、final等,还有是类类型还是接口类型。
一共有16个标志位可以使用,但是目前只定义了九个。
-
类索引,父类索引和接口索引的集合
类索引和父类索引分别用一个u2类型的数据结构表示,各自指向一个常量池中的CONSTANT_Class_info类型中的全限定名称,接口索引集合首先记录集合接口的数量,再指向每一个CONSTANT_Class_info(猜测)
-
字段表集合(field_info)
该字段包括类级变量以及实例中的变量,不包括方法中的局部变量。-
一. access_flags描述:描述类中或者接口中的变量。是一个u2的数据类型,每一个位代表一个bool值,比如:
-
字段的作用域;
-
类变量还是实例变量;
-
可变性(final)
-
并发可见性(volatile)
-
能否被序列化(transient)
-
字段数据类型(基本数据类型,对象,数组)
-
字段名称
二. name_index and descriptor_index:
都是对常量池的引用,分别对应简单名称描述符(都是u2)
字段和方法的描述符:用来描述数据类型,方法的参数列表,返回值,基本数据类型和void用一个大写字符表示,对象类型用大写L加上对象的全限定名表示。对于数组类型,用每一个维度用前置的[字符来描述。
描述符描述方法的时候,按照先参数列表,再返回值的顺序描述。不会列出从父类或父接口中继承来的字段,但是编译器有可能新加指向外面的字段。
三. (u2)attributes_count 和(attribute_info)attributes
descriptor_index后面可以跟的属性表集合,可以描述一个或者多项附加信息。
字段表中不会出现父类或者父接口中继承而来的字段信息,但是可能代码中不存在的字段信息(例如内部类为了访问外部增加外部的实例)
字段不可以重载,类中的字段名称必须都不一样
-
方法表集合
与字段表集合类似,但是没有volatile和transient字段,也可能会增加一些额外的方法。
另外,在java文件的特征签名(一个方法中各个参数在常量池中引用的集合)来验证是否是重载,由于返回值不包含在特征签名中,所以无法依靠返回值不同对一个方法进行重载。但是class文件中的特征前面是包括返回值的,也就是说只要返回值不同就可以同存在与一个class文件中。
方法里面的代码放入到Code属性中
-
属性表集合
用以描述class文件、字段表、方法表中的额外信息属性表的总体结构:
对于每一个属性,他的名称都要从常量池中引入一个CONSTANT_Utf8_info类型的常量表示,而属性的结构这是自定义的,只需要一个length说明属性的长度。
名称 类型 属性 u2 attribute_name_index 1 u4 attribute_length 1 u1 info attribute_length attribute_info又可细分为以下21种(即《Java虚拟机规范(Java SE 7)》中预定义了的21项虚拟机实现应当能识别的属性):
属性名称 使用位置 含义 Code 方法表中 Java代码编译成的字节码指令(即:具体的方法逻辑字节码指令) ConstantValue 字段表中 final关键字定义的常量值 Deprecated 类 中、方法表中、字段表中 被声明为deprecated的方法和字段 Exceptions 方法表中 方法声明的异常 LocalVariableTable Code属性中 方法的局部变量描述 LocalVariableTypeTable 类中 JDK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 InnerClasses 类中 内部类列表 EnclosingMethod 类中 仅当一个类为局部类或者匿名类时,才能拥有这个属性,这个属性用于表示这个类所在的外围方法 LineNumberTable Code属性中 Java源码的行号与字节码指令的对应关系 StackMapTable Code属性中 JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配 Signature 类中、方法表中、字段表中 JDK1.5新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息 SourceFile 类中 记录源文件名称 SourceDebugExtension 类中 JDK1.6中新增的属性,SourceDebugExtension用于存储额外的调试信息。如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码运行在Java虚拟机汇中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension就可以存储这些调试信息。 Synthetic 类中、方法表中、字段表中 标识方法或字段为编译器自动产生的 RuntimeVisibleAnnotations 类中、方法表中、字段表中 JDK1.5中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations属性,用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的。 RuntimeInvisibleAnnotations 类中、方法表中、字段表中 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations相反用于指明哪些注解是运行时不可见的。 RuntimeVisibleParameterAnnotations 方法表中 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations类似,只不过作用对象为方法的参数。 RuntimeInvisibleParameterAnnotations 方法表中 JDK1.5中新增的属性,作用与RuntimeInvisibleAnnotations类似,只不过作用对象为方法的参数。 AnnotationDefault 方法表中 JDK1.5中新增的属性,用于记录注解类元素的默认值 BootstrapMethods 类中 JDK1.7新增的属性,用于保存invokedynamic指令引用的引导方法限定符 -
Code属性
方法体内的代码经过javac编译之后成为字节码储存在code属性中,接口或者抽象类不存在code属性,
code属性结构表:
名称 类型 数量 作用 attribute_name_index u2 1 CONSTANT_Utf8_info attribute_length u4 1 max_stack u2 1 操作数栈最大深度,jvm站分配参考 max_locals u2 1 局部变量表所需的储存空间 code_length u4 1 字节码长度(jvm中只支持u2长度的函数) code u1 code_length 每个指令对应一个u1(最多有256种指令) exception_table_length u2 1 exception_table exception_info exception_table_length attribute_count u2 1 attributes attribute_info attribute_count 局部变量表可能引入的参数:
方法参数,包括this 显示处理异常的参数 方法体中定义的局部变量 ······ 任何实例方法中都会有一个this参数指向方法所属对象,在虚拟机调用实际方法的时候将this传进去。局部变量表中也会有预留出一个变量槽存放对象实例的引用。而static方法中没有该参数。
-
异常表格式:
类型 | 名称 | 数量 |
---|---|---|
u2 | start_pc | 1 |
u2 | end_pc | 1 |
u2 | handler_pc | 1 |
u2 | catch_type | 1 |
当从 start_pc行(相对于方法体的偏移量)到end_pc行(不包括)如果出现了catch_type(指向一个CONSTANT_Class_info)或者其子类的异常,则转到handler_pc行进行处理。
注意:每段分支之后的代码都将用finally代码重复一次,比如try之后加一段finally代码,catch之后也会有一段finally代码。
-
Exeptions属性
与Code属性平级,,列出方法中可能会出现的受查异常。也就是throws抛出的异常。
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
number_of_exceptions | u2 | 1 |
exception_index_table | u2 | number_of_exceptions |
-
LIneNumberTable属性
描述源码行号与字节码行号的属性。不是默认的属性,可以使用-g:none或者-g:lines关闭。
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
line_number_table_length | u2 | 1 |
line_number_table | line_number_info | line_number_table_length |
line_number_info包含start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是源码行号。
如果关闭了抛出异常的时候就不会显示抛出异常的行号。
-
LocalVariableTable和LocalVariableTypeTable属性
LocalVariableTable描述栈帧中局部变量表与Java源码中定义的变量的关系,其中也有包括改变了在字节码中作用域范围(用start_pc与length一同决定)
LocalVariableTypeTable可以用作记录泛型类型的一个属性。记录局部变量表中的泛型
-
SourceFile与SourceDebugExtension属性
SourceFile记录生成这个class文件源码文件的名称
一个类最多存在一个SourceDebugExtension属性,用于额外的代码调试信息。
-
ConstantValue属性
通知虚拟机自动为静态变量赋值。
-
对于非static的变量在实例构造器<init>中赋值
-
Oracle实现的机制是:
-
同时使用static和final修饰。并且数据类型是String或者基本数据类型的化就是用ConstantValue来初始化。
-
没有被final修饰或者是其他类型会选择在<clinit>中初始化。
-
-
InnerClass属性
InnerClass属性用于记录内部类与宿主类之间的关联
名称 类型 数量 attribute_name_index u2 1 attribute_length u4 1 number_of_class u2 1 inner_classes inner_classes_info number_of_class number_of_classes代表记录了多少个内部类信息,每一个内部类信息都由一个inner_classes_info表进行描述。inner_classes_info结构为:
名称 类型 数量 inner_class_info_index u2 1 outer_class_info_index u2 1 inner_name_index u2 1 inner_class_access_flags u2 1
-
Deprecated和Synthetic属性
只存在有和没有的区别
通过@Deprecate注解使用,代表某个类、字段或者方法已经被弃用
Synthetic是由编译器自行添加的,生成源代码中的一些不存在的方法、字段甚至是整个类,通过这种方式实现了越权发个文。比如枚举类中自动生成的枚举元素数组和嵌套类的桥接方式,所有不属于用户产生的类、方法、字段都应该设置Synthetic,除了init和clinit方法。
-
StackMaptable属性
在类加载的验证作用,在编译期间将一系列的验证类型直接记录在class文件中,通过检验这些验证类型从而省去了类型推导的过程,提升字节码的验证能力,一个方法的code属性最多有一个StackMapTable类型,没错,该类型用以修饰Code属性。
-
Signature
可选的定长属性,可出现在类、字段、方法表的属性中。
泛型签名如果包含了类型变量或参数化类型,SIgnature会为他记录泛型类型签名信息
-
BootstrapMethods
保存invokedynamic指令引用的引导方法限定符,常量池中如果出现CONSTANT_InvokeDynamic_info类型的变量那么后面必须有BootstarpMethods属性(只能有一个)
BootstrapMethods_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{ u2 bootstrap_method_ref;//必须是一个CONSTANT_MethodHandle_info结构
u2 num_bootstrap_arguments;
u2 bootstrap_arguments[num_bootstrap_arguments];
} bootstrap_methods[num_bootstrap_methods];
}
-
MethodParameters
记录方法中各个形参的名称信息,可以通过反射api获取。
-
模块化相关的属性
包括Module、ModulePackages、ModuleMainClass三个属性用来支持java的模块化属性。
-
Module: 除了该模块的名称、版本、标志信息、模块requires、exports、opens、uses、provides定义的全部内容
-
ModulePackages 描述该模块中所有的包
-
ModuleMainClass 描述该模块的主类
-
运行时注解的相关属性
例如RuntimeVisibleAnnotations记录了类、字段、方法的声明上记录运行时可见的注解。
字节码指令简介
byte | short | int | long | double | float | char | reference |
---|---|---|---|---|---|---|---|
bi | si | i | l | d | f | ci | a |
-
很多Boolean、char、byte、short类型的操作实际上使用int的指令操作的
-
加载和储存指令
-
将局部变量加载到操作栈:iload、iload_<n>(代表iload0,iload1--iloadn这几个指令)……
-
将一个数值从操作数栈储存到局部变量表:istore、istore_n……
-
将一个常量加载到操作数栈:bipush、ldc、ldc_w、aconst_null……
-
扩充局部变量表的访问索引的指令:wide
-
-
运算指令:
-
xadd
-
xsub
-
xmul
-
xdiv
-
求余:xrem
-
取反:xneg
-
位移指令:xshl
-
按位或指令:xor
-
按位与指令:xand
-
按位异或指令:xxor
-
局部自增指令:xinc
-
比较指令:xcmpg、xcmpl
-
-
类型转换指令: 强制:x2y
-
对象创建指令
-
操作数栈管理指令
-
控制转移指令
-
方法调用和返回指令
-
异常指令
-
同步指令
https://www.jianshu.com/p/e6d4e0f01228