Class类文件的结构
概念
- Class文件是一组以8位字节为基础单位的二进制流
- 按顺序整齐排列
- 没有分隔符
- 排序方式:高位在前
- 类似于C语言结构体的伪结构来存储
- 只有两种数据类型:
- 魔数
- 紧接着版本号
- Class文件中的资源仓库
- 常量池容量计数值(constant_pool_count)
- 常量池中常量数量不确定,所以需要一个计数
- 是u2类型的数据
- 从1开始而不是从0开始计数
- 设计者将0位做特殊考虑,特殊情况下,有些指向常量池的索引数据要表达“==不引用任何常量池项目==”的意义。
- 常量池容量(偏移地址:0x00000008)为十六进制数0x0016,十进制22,表示常量池中有21项常量,索引1~21。
- 主要存放类型
- 字面量(Literal):如文本字符串、final常量等
- 符号引用(Symbolic References)
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 14种常量池的每个常量表
- CONSTANT_Utf8_info 1 UTF-8
包含的类型:- u1 tag 1 标志位
- u2 length 1 utf-8字符串的长度
- u1 bytes length 真正的字符串内容
- CONSTANT_Integer_info 3 整型
- CONSTANT_Float_info 4 浮点型
- CONSTANT_Long_info 5 长整型
- CONSTANT_Double_info 6 双精
- CONSTANT_Class_info 7 类或接口的符号引用
- CONSTANT_String_info 8 字符串型
- CONSTANT_Fieldref_info 9 字段的符号引用
- CONSTANT_Methodref_info 10 类中方法的符号引用
- CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
- CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
- CONSTANT_MethodHandle_info 15 方法句柄
- CONSTANT_MethodType_info 16 标识方法类型
- CONSTANT_InvokeDynamic_info 18 动态方法调用点
- CONSTANT_Utf8_info 1 UTF-8
- java代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55PS E:\> javac .\TestClass.java
PS E:\> javap -verbose .\TestClass.class
Classfile /E:/TestClass.class
Last modified 2018-8-9; size 293 bytes
MD5 checksum a364fba0fc6304868b1a4cc1a3ba3729
Compiled from "TestClass.java"
public class org.laowang.clazz.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 // org/laowang/clazz/TestClass.m:I
#3 = Class #17 // org/laowang/clazz/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 org/laowang/clazz/TestClass
#18 = Utf8 java/lang/Object
{
public org.laowang.clazz.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
}
SourceFile: "TestClass.java"
访问标志(access_flags)
- 紧接着常量池
- 两个字节
- 总共16位,当前只定义了其中8个,未使用的一律为0
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | public |
ACC_FINAL | 0x0010 | final |
ACC_SUPER | 0x0020 | invokespecial(JDK1.0.2之后编译出来的类这个标志欧必须为真) |
ACC_INTERFACE | 0x0400 | abstract(接口或者类) |
ACC_SYNTHETIC | 0x1000 | 并非由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 注解 |
ACC_ENUM | 0x4000 | 枚举 |
类索引(this_class)、父类索引(super_class)与接口索引(interfaces)集合
- 紧接着访问标志
- u2类型(this_class、super_class)
- u2类型集合(interfaces)
- 各索引的含义
- 这三项数据来确定类的继承关系
- 类索引(this_class)用来确定类全限定名
- 父类索引(super_class)用来确定父类的全限定名
- 接口索引描述类实现的接口,按从左到右的顺序排列。
- 父类索引(super_class)除了java.lang.Object外,其他的类都不为0。
字段表集合(field_info)
- 描述接口或类中声名的变量
- 包含:
- 作用域(public private protected)
- 实例变量还是类变量(static)
- 可变性(final)
- 并发可见性(volatile)
- 序列化(transient)
- 字段数据类型
- 字段名称
- 全限定名,相当于类的全路径,只是把.换成/,如java/lang/Object
- 简单名称,方法或属性名的简写,如inc()方法和m属性,则简单名称为inc和m
- 描述符:
- 描述字段的数据类型、方法的参数列表和返回值。
- 基本数据类型和void都用一个大写字母来表示,对象类型用L加全限定名来表示
标识名称 | 含义 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | 对象类型 |
- 数组描述符:
- 一维数组:String[] –> [Ljava/lang/String
- 二维数组:String[][] –> [[Ljava/lang/String
- 方法描述符:
- 包含
- 访问标志(access_flags)
- 名称索引(name_index)
- 描述符索引(descriptor_index)
- 属性表集合(attributes)
- 方法表集合中没有方法里面的代码,Java代码存在属性表集合中名为“Code”的属性里面
- 重写和重载的原理
- 描述某些场景的专有信息
- 没有太多顺序、长度、内容的限值
- 虚拟机规范预定于的属性
属性名 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 只有当一个类是局部类或匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
StackMapTable | Code属性 | JDK1.6新增,供新的类型检查验证器检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配 |
Signature | 类、方法表、字段表 | JDK1.5新增,支持泛型情况下的方法签名 |
SourceFile | 类文件 | 记录源文件名 |
SourceDebugExtension | 类文件 | JDK1.6新增,用于存储额外的调试信息 |
Synthetic | 类、方法表、字段表 | 标识方法或字段为编译器自动生成的 |
LocalVarialbleTypeTable | 类 | JDK1.5新增,使用特征签名代替描述符 |
RuntimeVisibleAnnotations | 类、方法表、字段表 | JDK1.5新增,为动态注解提供支持,指明哪些注解是运行时可见的 |
RuntimeInvisibleAnnotations | 类、方法表、字段表 | JDK1.5新增,指明哪些注解是运行时不可见的 |
RuntimeVisibleParameterAnnotations | 方法表 | JDK1.5新增,与RuntimeVisibleAnnotations类似,只不过作用对象为方法参数 |
RuntimeInvisibleParameterAnnotations | 方法表 | JDK1.5新增,与RuntimeInvisibleAnnotations类似,只不过作用对象为方法参数 |
AnnotationDefault | 方法表 | JDK1.5新增,用于记录注解类元素的默认值 |
BootstrapMethods | 类文件 | JDK1.7新增,用于保存invokedynamic指令引用的引导方法限定符 |
Code属性
- Java编译器处理后的字节码指令存储在Code属性内
- Code属性出现在方法表的属性集合中
- 并非所有的方法表都必须存在这个属性
- 结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index:指向CONSTANT_UTF8_info型常量的索引 | 1 |
u4 | attribute_length:属性值的长度 | 1 |
u2 | max_stack:操作数栈的最大深度 | 1 |
u2 | max_locals:局部变量表所需的存储空间 | 1 |
u4 | code_length:编译后的字节码指令长度 | 1 |
u1 | code:编译后的字节码指令流 | code_length |
u2 | exception_table_length:异常表长度 | 1 |
excpetion_info | exception_table:异常表内容 | excpetion_table_length |
u2 | attributes_counts: 属性数量 | 1 |
attribute_info | attributes: 属性内容 | attributes_counts |
- Code属性是Class文件中最重要的一个属性,如果把Class文件就分为代码(Code)和元数据(Metadata)两部分。那么Code可以解释为描述代码,元数据解释为其他数据。
- attribute_name_index:固定为“Code”,表示该属性的名字
- attribute_length:由于属性名和属性长度一共6个字节(u2+u4),所以属性值的长度就是整个属性表的长度减去这两个属性。
- max_stack:在方法执行的任意时刻,操作数栈都不会超过这个长度。虚拟机运行的时候根据这个值来分配栈帧中的操作栈深度。
- max_locals:单位是Slot,是虚拟机为局部变量分配内存的最小单位。(参考对应01、虚拟机 VM Stack)
- Slot可重用:并不是方法中使用了多少个局部变量,就把这些局部变量所占Slot之和作为max_locals。当代码执行超出一个局部变量的作用域时,这个局部变量所占的Slot可以被其他局部变量所使用。
- code_length:code_length代表编译后的字节码长度,code是存储字节码指令的一系列字节流。
- exception_table_length:不是必须存在的。
this关键字
- 在任何实例方法里面,都可以通过this关键字访问到此方法所属的对象。
- 它的实现其实非常简单,仅仅是通过Javac编译器编译的时候把对this关键字的访问转变为对一个普通方法参数的访问,然后在虚拟机调用实例方法时自动传入此参数而已。
- 因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量。
- 局部变量表中也会预留出第一个Slot位来存放对象实例的引用,方法参数值从1开始计算。这个处理只对实例方法有效,而对静态方法无效。
例:
有效:
1
2
3
4
5
6
7
8
9
10package org.laowang.clazz;
public class TestClass{
private int m;
public int inc(){
return m+1;
}
}无效:
1
2
3
4
5
6
7
8
9
10package org.laowang.clazz;
public class TestClass{
private static int m;
public static int inc(){
return m+1;
}
}
Exceptions属性
列举出方法中可能抛出的受查异常(Checked Exceptions),也就是方法描述时在throws关键字后面列举的异常。结构如下:
类型 | 名称 | 数量
—|—|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
u2 | number_of_exceptions | 1
u2 | exception_index_table | number_of_exceptions
LineNumberTable属性
- 描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。
- 不是运行时必须的属性,但默认会生成到Class文件中
- 可以在Javac中分别使用-g:none或-g:lines来取消或者要求生成这项信息。
- 如果不生成这个属性,最大的影响是当抛出异常时,堆栈中部会显示出错的行号,并且在调试的时候,也无法按照源码来设置断点。
LocalVariableTable属性
- 描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系
- 不是运行时必须的属性,但是会默认生成到Class文件中。
- 可以在Javac中分别使用-g:none或-g:vars来取消或者要求生成这项信息。
- 如果不生成这个属性,最大的影响是当其他人引用这个方法时,所有的参数名称都回丢失,IDE将会使用诸如arg0、arg1之类的占位符代替原有的参数名,但对程序运行没有影响。
SourceFile属性
- 记录生成这个Class文件的源码文件名称。
- 可选的属性。
- 可以在Javac中分别使用-g:none或-g:source来关闭或者要求生成这项信息。
- 如果不开启,最大的影响是内部类抛出异常的时候,堆栈中将不会显示出错代码所属的文件名。
ConstantValue属性
- 通知虚拟机自动为静态变量赋值。
- 记录内部类与宿主类之间的关联。
- 如果一个类定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。
Deprecated及Synthetic属性
- Deprecated: 使用@Deprecated注解进行设置,表示不再推荐使用,过时了。
- Synthetic:表示字段或方法并不是由Java源码直接产生的,而是由编译器添加的。
StackMapTable属性*
Signature属性*
BootstrapMethods属性*