标签:lazy rap nal 过多 获取 继承 iad class 网上
class文件格式采用的是类似于C语言结构体的伪结构来存储数据,这种伪结构种只有两种数据类型:“无符号型”和表
类型 | 名称 | 数量 |
---|---|---|
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_counts(接口数量) | 1 |
u2 | interfaces(接口名) | interfaces_count |
u2 | fileds_count(域数量) | 1 |
file_info | fileds(域名) | fields_count |
u2 | methods_counts(方法数量) | 1 |
method_info | methods(方法名) | methods_count |
u2 | attributes_count(属性数量) | 1 |
attribute_info | attributes(属性) | attributes_count |
Class的版本号是跟在魔数后面的
Java的版本号是从45开始的,每个JDK大版本发布主版本号向上加1,高版本兼容低版本,但是不能运行高于虚拟机版本号的Class文件。
JDK版本 | 16进制 | 版本号 |
---|---|---|
1.2 | 0X002E | 46 |
1.3 | 0X002F | 47 |
1.4 | 0X0030 | 48 |
1.5 | 0X0031 | 49 |
1.6 | 0X0032 | 50 |
1.7 | 0X0033 | 51 |
1.8 | 0X0034 | 52 |
9 | 0X0035 | 53 |
10 | 0X0036 | 54 |
常量池是紧接着主、次版本号之后的,可以简单的理解为Class文件资源仓库
由于常量池种常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量容量计数值(constant_pool_count),这个容量计数值是从1开始而不是0,是因为如果后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示;入上图所示2C,十进制45 代表着常量吃中有44个常量 索引1~44
Class文件结构中只有常量池容量计数器是从1开始。对于其他的集合类型,包括接口索引集合、字段表集合等都是从0开始
package com.jack.test;
public class TestClass {
private int m;
public int inc(){
return m + 1;
}
}
上图是二进制class中可以看出,0X0013表示的是19,常量的数量是18个,通过javap 命令可以打印是18个常量
E:\learn\java\algs-data-structure\src\main\java\com\jack\test>javap -verbose T
estClass.class
Classfile /E:/learn/java/algs-data-structure/src/main/java/com/jack/test/TestC
lass.class
Last modified 2020-7-19; size 289 bytes
MD5 checksum a14137a5f8affdff932d5fb251c12a09
Compiled from "TestClass.java"
public class com.jack.test.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#16 // com/jack/test/TestClass.m:I
#3 = Class #17 // com/jack/test/TestClass
#4 = Class #18 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 inc
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 TestClass.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = NameAndType #5:#6 // m:I
#17 = Utf8 com/jack/test/TestClass
#18 = Utf8 java/lang/Object
常量池中主要存放两大常量类:字面常量、符号引用
字面常量:和Java语言层面的常量概念差不多,文本字符串、被声明为final的常量
符号引用:属于编译原理方面的概念
常量池中每一项常量都是一个表,每个表代表着不同类型的常量,这些表有一个共同的特性,就是表结构的起始第一位是一个u1类型的标志位,来表示当前常量是属于那种常量类型。常量池表结构如下图所示:
访问标志是跟在常量池结束之后的2个字节代表,主要用于识别一些类或者接口层次的访问信息。
比如:这个Class是类还是接口;是否定义为public类;是否定义为abstract类型;如果是类的话是否声明为final等等
图片中只定义了九个标志位,没有使用的的标志位一律要求为零;具体如下图所示:
1、类索引和父类索引都是一个u2类型的数据;
2、类索引 用来确定这个类的全限定名,父类索引用来确定这个类的父类全限定名;
3、因为在Java中单继承,父类的索引只有一个,除了java.lang.Object外,所有的Java类都有父类。
4、各自指向一个类型CONSTANT_Class_info的类描述符常量
字段表用来描述接口或者类中声明的变量,包括类级变量和实例变量,但是不包括方法内部声明的局部变量
跟随access_flags后面的两项索引值:name_index、descriptor_index分别代表着字段的简单名称以及字段和方法的描述符
简单名称:就是指没有类型和参数修饰的方法或者字段名称
描述符:用来描述字段数据类型、方法参数列表(数量、类型、顺序)和返回值
根据描述符规则,基础数据类型以及void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示
对于数据,每一个维度用一个前置“[”来表示
如定义一个java.lang.String[] 对应的就是“[Ljava/lang/String;”;如果是二维数据 “[[Ljava/lang/String;”
描述符用来描述方法时,按照先参数列表、后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号中“()”之类;
比如:void inc() --->"()V"
? int inc (Char[] str,int i) ---> ([CI)I
在descriptor_index后面跟随的是一个属性集合表,主要用来存储一些额外的信息,这个在后面会详细的写
方法表集合的表结构和字段表结构差不多,仅在访问标志和属性表集合有所区别
方法表访问标志:
特征签名:Java方法特征签名只包括方法名称、参数顺序和参数类型,在字节码的特征签名还包括方法返回值和受异常表
在Java语言中,方法是存在重载的,重载的定义是方法名相同,参数列表不同,返回值可以相同也可以不同。
要重载一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名。所以在java中方法重载和返回值无关。
属性表在Class文件、字段表、方法表中都可以携带自己的属性表集合,以描述某些场景专有的信息。与Class文件中其他的数据项目要求严格的舒徐、长度和内容不同。属性表集合相对稍微宽松一些,允许只要已有属性名不重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息。
虚拟机规范定义的属性
这些图片都是网上找的,一个一个太难写了,这些都是JDK1.7之前的,在之后还有新增一些其他的,具体的参看相关书籍和文档
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的格式则是完全自定义,只需要通过一个u4的长度属性去说明属性值所占用的位数即可
在Java程序方法体中的代码经过javac编译之后,最终为字节码指令存储在Code属性中,Code属性出现在方法表的属性中,但是并不是一定就存在的,比如抽象类和接口中就不存在Code属性
Code属性表结构
源码
package com.jack.test;
public class TestClass {
private int m;
public int inc(){
return m + 1;
}
public static void test(int i){
}
}
编译之后的方法字节码指令
{
public com.jack.test.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 7: 0
public static void test(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 12: 0
}
从编译之后的字节码可以看出args_size=1,这个是为什么呢,方法是没有传参的。在Java中有一个关键字 this访问到此方法的对象属性。this关键字的访问转变为对一个普通方法参数的访问,然后在虚拟机调用实例方法时自动传入此参数而已。因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部标量表中也会预留出来第一个变量槽位来存放对象实例引用,所以实例方法参数值从1开始计算。这个只是对实例方法有效,如果是static(静态方式)那么args_size=0而不是1;从上如的中test()方法中可以看出。
? Exception属性是和Code属性平级的一项属性,它的作用是列举处方法中可能抛出的受查异常(Checked Expcetions),也就是方法描述是在throws关键字后面列举的异常。
Exception属性结构:
number_of_exceptions表示方法可能抛出number_of_exceptions中受查异常,每一种受查异常用一个exception_index_table项表示,exception_index_table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型
? LineNumberTable属性用于描述java源码行号和字节码行号之间的对应关系。它并不是运行时必须的属性,但默认会生成到Class文件中,可以再javac中分别使用-g:none或-g:lines选项取消或要求生成这项信息。如果选择不生成LineNumberTable属性,当程序抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行设置断点
LineNumberTable属性结构:
line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合,line_number_info表包含了start_pc和line_number两个u2类型的数据项,前者表示字节码行号,后者表示java源码行号
? LocalVariableTable属性用于描述栈帧中局部变量表中的变量与java源码中定义的变量的关系,它也不是运行时必须数据,但默认会生成在Class文件中。可以在javac时使用-g:none或-g:lines来取消或要求生成这项信息。如果不生成这个信息,当其他人引入这个方法时,所有参数名称会丢失,IDE会使用诸如arg1、arg2之类的占位符替代原有的参数名,这对程序运行没有影响,但是对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获取参数值。
LocalVariableTable属性结构:
其中local_variable_info 项代表了一个栈帧与源码中局部变量的关联,local_variable_info表结构如下所示:
? SourceFile属性用于记录生成这个Class文件的源码名称。这个属性也是可选的,可以分别使用javac 的 -g :none或-g:source来关闭或要求生成这项信息。如果不生成这项信息,当抛出异常,堆栈中将不会显示出错代码所属的文件名。这个属性是一个定长的属性。
SourceFile属性结构:
sourcefile_index数据项时指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件名
ConstantValue属性主要的作用是通知虚拟机自动为静态变量赋值。只有static修饰的变量(类变量)才可以使用这项属性。
比如: int=123 和static int = 234
虚拟机对这两种变量赋值的方式和时刻是有所不同的:
对于非static类型的变量(也就是实例变量)的赋值是在实例构造器
对于类变量则有两种方式可以选择:在类构造器
对于目前oracle公司的javac来编译:
1、如果同时使用fianl和static来修饰一个变量,并且这个变量的数据类型和基本类型或是java.lang.String类型,将会使用ConstantValue属性来进行初始化
2、如果这个变量没有被final修饰,或者是非基本类型及字符串,则将会选择在
public class TestClass {
int x = 123;
static int y = 234;
final static int m = 456;
}
Last modified 2020-7-26; size 320 bytes
MD5 checksum e282601d538c42886aba938a359a39ca
Compiled from "TestClass.java"
public class com.jack.test.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#16 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#17 // com/jack/test/TestClass.x:I
#3 = Fieldref #4.#18 // com/jack/test/TestClass.y:I
#4 = Class #19 // com/jack/test/TestClass
#5 = Class #20 // java/lang/Object
#6 = Utf8 x
#7 = Utf8 I
#8 = Utf8 y
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 <clinit>
#14 = Utf8 SourceFile
#15 = Utf8 TestClass.java
#16 = NameAndType #9:#10 // "<init>":()V
#17 = NameAndType #6:#7 // x:I
#18 = NameAndType #8:#7 // y:I
#19 = Utf8 com/jack/test/TestClass
#20 = Utf8 java/lang/Object
{
int x;
descriptor: I
flags:
static int y;
descriptor: I
flags: ACC_STATIC
public com.jack.test.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 123
7: putfield #2 // Field x:I
10: return
LineNumberTable:
line 3: 0
line 4: 4
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: sipush 234
3: putstatic #3 // Field y:I
6: return
LineNumberTable:
line 6: 0
}
SourceFile: "TestClass.java"
E:\learn\java\algs-data-structure\src\main\java\com\jack\test>javac TestClass.java
E:\learn\java\algs-data-structure\src\main\java\com\jack\test>javap -verbose TestClass.class
Classfile /E:/learn/java/algs-data-structure/src/main/java/com/jack/test/TestClass.class
Last modified 2020-7-26; size 361 bytes
MD5 checksum 96131d509457480815063e6bce4cdbde
Compiled from "TestClass.java"
public class com.jack.test.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#19 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#20 // com/jack/test/TestClass.x:I
#3 = Fieldref #4.#21 // com/jack/test/TestClass.y:I
#4 = Class #22 // com/jack/test/TestClass
#5 = Class #23 // java/lang/Object
#6 = Utf8 x
#7 = Utf8 I
#8 = Utf8 y
#9 = Utf8 m
#10 = Utf8 ConstantValue
#11 = Integer 456
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 <clinit>
#17 = Utf8 SourceFile
#18 = Utf8 TestClass.java
#19 = NameAndType #12:#13 // "<init>":()V
#20 = NameAndType #6:#7 // x:I
#21 = NameAndType #8:#7 // y:I
#22 = Utf8 com/jack/test/TestClass
#23 = Utf8 java/lang/Object
{
int x;
descriptor: I
flags:
static int y;
descriptor: I
flags: ACC_STATIC
static final int m;
descriptor: I
flags: ACC_STATIC, ACC_FINAL
ConstantValue: int 456
public com.jack.test.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 123
7: putfield #2 // Field x:I
10: return
LineNumberTable:
line 3: 0
line 4: 4
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: sipush 234
3: putstatic #3 // Field y:I
6: return
LineNumberTable:
line 6: 0
}
SourceFile: "TestClass.java"
InnerClasses属性是用来记录内部类和宿主类之间的关联。如果一个类中定义了内部类,那么编译器将会为它以及它包含的内部类生成InnerClasses属性。
InnerClasses属性表结构:
number_of_classes记录类中有多少个内部类,每个内部类的信息都是由一个inner_classes_info属性表来描述。
inner_classes_info属性表结构:
inner_class_info_index和outer_class_info_index都指向常量池中CONSTANT_Class_info常量索引,分别代表了内部类和宿主类的符号引用。
inner_name_index:指向常量池中CONSTANT_Utf8_info常量索引,代表这个内部类中的名称,如果是匿名内部类,这项值为0
inner_class_access_flags:内部类的访问标志,类似于access_flags,取值范围如下图所示:
Deprecated和Synthetic两个属性都属于标志类型的布尔类型,只存在有和没有区别,没有属性值的概念。
Deprecated:用于表示某个类、字段、方法,已经被程序作者定义为不推荐使用,过时了。在java代码中使用@Deprecated注解来标志
Synthetic:代表此字段或者方法不是有Java源码直接产生的,而是由编译器自行添加的
Deprecated与Synthetic属性结构:
StackMapTable属性在JDK1.6增加到Class文件规范中的,它是一个相当复杂的边长属性,位于Code属性中。这个序性会在虚拟机加载的字节码验证阶段被新类型检查器使用。母的在于代替之前比较消耗性能的基于数据流分析的类型推导验证器。
StackMapTable属性:
类型检查器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。
StackMapTable属性结构:
Signature属性在JDK1.5发布后增加到Class文件规范中,它是一个可选的定长属性,可以出现于类、属性表和方法表结构的属性表中。在任何类、接口、初始化方法或成员的泛型简明中如果包含了类型变量(TypeVariable)或参数化类型(Parameterized Type),则Signature属性会为它记录泛型签名信息。
使用Signature属性去记录泛型类型的原因是:
Java语言的泛型采用的是擦除法实现的伪泛型
字节码(Code属性)中所有的泛型信息编译(类型变量、参数化类型)在编译之后都通通擦除。使用擦除法好处是实现简单,但是坏处是运行期间无法像C#等有真泛型
Signature属性结构:
? BootstrapMethods属性是在JDK1.7发布后增加到CLass文件规范中,它是一个复杂的变长属性,位于类文件表中。这个属性用于保存invokedynamic指令引用的引导方法限定符。
? 如果某个类文件的常量池中曾经出现过CONSTANT_InvokeDynamic_info类型的常量,那么这个类文件的属性表中必须存在一个明确的BootstrapMethods属性,另外,即使CONSTANT_InvokeDynamic_info类型的常量在常量池中出现过多次,最多也只能有一个BootstrapMethods属性。
BootstrapMethods属性表结构:
其中引用到的bootstrap_method结构如下:
BootstrapMethod属性中的num_bootstrap_methods项的值是一个bootstrap_methods[]数组中的引导方法限定符的数量,bootstrap_methods[]数组中的每个成员包含了一个指向常量池CONSTANT_MethodHandle结构的索引值,它代表一个引导方法。还包含了这个引导方法静态参数的序列。
bootstrap_methods[]数组的每个成员必须包含以下三项:
bootstrap_method_ref:该项的值必须是一个对常量池的有效索引,常量池在该索引处的值必须是一个CONSTANT_MethodHandle_info结构
num_bootstrap_arguments:该项的值给出了bootstrap_arguments[]数组成员数量
bootstrap_arguments[]:该数组的每个成员必须是一个常量池的有效索引
MethodParameters属性是在JDK1.8新加入到Class文件格式中的,它是一个用在方法表表中的变长属性。MethodParameters的作用是记录方法的各个形参名称和信息。
MethodParameters属性结构:
类型 | 数量 | |
---|---|---|
u2 | attribure_name_index | 1 |
u4 | attribute_length | 1 |
u1 | parameters_count | 1 |
parameter | parameters | parameters_count |
其中引用到的parameter结构如下:
类型 | 名称 | 数量 |
---|---|---|
u2 | name_index | 1 |
u2 | access_flag | 1 |
name_index:是一个指向常量池CONSTANT_Utf8_info常量的索引
access_flag:是参数的状态指示器,它可以包含以下三种状态中的一种或者多种
JDK9中一个重量级的功能是Java的模块化功能,因为模块描述文件最终是要编译成一个独立的Class文件来存储的,所以Class文件格式也扩展了Module、ModulePackages和ModuleMainClass三个属性用于支持Java模块化功能。
? Module属性是一个非常复杂的边长属性,除了表示该模块的名称、版本、标志信息以外,还存储了这个模块requires、exports
opens、uses和provides定义的全部内容。
? Module属性表结构:
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | module_name_index | 1 |
u2 | module_flags | 1 |
u2 | module_versin_index | 1 |
u2 | requires_count | 1 |
require | requires | require_count |
u2 | exports_count | 1 |
export | exports | export_count |
u2 | opens_count | 1 |
open | opens | open_count |
u2 | uses_count | 1 |
use | uses_index | ues_count |
u2 | provides_count | 1 |
provide | provides | provide_count |
exports属性的每一个元素都代表一个被模块所导出的包。exports属性表结构如下所示:
类型 | 名称 | 数量 |
---|---|---|
u2 | exports_index | 1 |
u2 | exports_flags | 1 |
u2 | exports_to_count | 1 |
export | exports_to_index | exports_to_count |
ModulePackagess属性是另一个用于支持Java模块化的变长属性,用于描述该模块中所有的包,不管是不是export或者open的
ModulePackagess属性结构如下:
类型 | 名称 | 描述 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | package_count | 1 |
u2 | package_index | package_count |
参考:深入理解Java虚拟机第三版
标签:lazy rap nal 过多 获取 继承 iad class 网上
原文地址:https://www.cnblogs.com/JackQiang/p/13380059.html