1 简述
class文件是JVM虚拟机能够识别的文件格式,目前Java、kotlin等语言都会编译成该格式文件,让JVM能够加载运行。
2 格式
在字节码文件中只存储了两种类型的数据:
- 无符号数u1(u2、u3、u4...),表示1个字节(2个字节、3个、4个...),
- 表(无符号的集合)。 | 类型 | 名称 | 数量 | 数量 | | --- | --- | --- | --- | | 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 | attributes_count | 1 | | | attribute_info | attributes | attributes_count | |
3 具体内容
magic
魔数,CAFEBABE,标记文件类型为Class文件
minor_version
次版本号
major_version
主版本号
constant_pool_count
常量池中常量的个数,有这个数,JVM才知道申请多少空间。
从1开发,将第0项常量空了出来,这是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可用索引值0来表示。
constant_pool
常量池,用于存放其结构及其子结构中引用的各种字符串常量、类和接口名称、字段名称和其他常量。
constant_pool表的索引从 1 到constant_pool_count- 1。
| 常量池中数据项类型 | 类型标志 | 类型描述 |
|---|---|---|
| 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 | 对一个字段或方法的部分符号引用 |
| CONSTANT_MethodHandle | 15 | JAVA SE 7 |
| CONSTANT_MethodType | 16 | JAVA SE 7 |
| CONSTANT_Dynamic | 17 | JAVA SE 11 |
| CONSTANT_InvokeDynamic | 18 | JAVA SE 7 |
| CONSTANT_Module | 19 | JAVA SE 9 |
| CONSTANT_Package | 20 | JAVA SE 9 |
常量池主要存放两大类常量:
- 字面量
- 符号引用
字面量
文本字符串、声明为final的常量值、基本数据类型等
符号引用
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
全限定名:a/b/c/D 这个就是类的全限定名,仅仅是把包名的“.“替换成”/”,为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“;”表示全限定名结束。
简单名称:是指没有类型和参数修饰的方法或者字段名称,上面例子中的类的add()方法和num字段的简单名称分别是add和num
描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。
| 标志符 | 含义 |
|---|---|
| B | 基本数据类型byte |
| C | 基本数据类型char |
| D | 基本数据类型double |
| F | 基本数据类型float |
| I | 基本数据类型int |
| J | 基本数据类型long |
| S | 基本数据类型short |
| Z | 基本数据类型boolean |
| V | 代表void类型 |
| L | 对象类型,比如:Ljava/lang/Object; |
| [ | 数组类型 |
Java在没有加载进JVM时,是没有分配内存空间的,也就是我们的方法里如果new一个类,class文件无法确定对象的地址,这个时候需要用符号引用去标识,new 的是哪个类。
这样运行的时候才能去new到正确的类,将符号引用转为直接地址引用。
access_flags
class文件访问标识信息
| 标志名 | 标志值 | 标志含义 | 针对的对像 |
|---|---|---|---|
| ACC_PUBLIC | 0x0001 | public类型 | 所有类型 |
| ACC_FINAL | 0x0010 | final类型 | 类 |
| ACC_SUPER | 0x0020 | 使用新的invokespecial语义 | 类和接口 |
| ACC_INTERFACE | 0x0200 | 接口类型 | 接口 |
| ACC_ABSTRACT | 0x0400 | 抽象类型 | 类和接口 |
| ACC_SYNTHETIC | 0x1000 | 该类不由用户代码生成 | 所有类型 |
| ACC_ANNOTATION | 0x2000 | 注解类型 | 注解 |
| ACC_ENUM | 0x4000 | 枚举类型 | 枚举 |
| ACC_MODULE | 0x8000 | 模块类型 | 模块 |
this_class
类索引,用于确定该类的全限定名。
super_class
确定父类全限定名的索引,除了Object类的为0,其他类都有父类索引。
类索引和父类索引各自指向一个类型为CONSTANT_Class_info类型的常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTATN_Utf8_info类型的常量中的全限定名字符串。
interfaces_count
当前类直接实现的接口的数量或当前接口直接继承的接口的数量。
interfaces
对当前类或当前接口直接实现或继承的所有接口的描述。
fields_count
fields_count描述字段表的个数。
fields
字段表用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
methods_count
methods中method_info的数目。
methods
method_info {
u2 access_flags; // 访问标志 public private protected static
u2 name_index; // 方法名索引
u2 descriptor_index; // 描述符索引
u2 attributes_count; // 属性计数器
attribute_info attributes[attributes_count]; // 属性集合
}
attributes_count
属性表长度
attributes
方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该class文件的源文件的名称。以及任何带有RetentionPolicy.CLASS 或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试,一般无须深入了解。
此外,字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息。
attribute_info {
u2 attribute_name_index; // 属性名索引
u4 attribute_length; // 属性长度
u1 info[attribute_length]; // 属性表
}
| 属性名称 | 使用位置 | 含义 |
|---|---|---|
| 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指令引用的引导方法限定符 |