【深入浅出-JVM】(69):class文件
结构
结构体
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flag; u2 this_class; u2 super_class; u2 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]; }
例子
package com.mousycoder.mycode.thinking_in_jvm; /** * @version 1.0 * @author: mousycoder * @date: 2019-08-06 14:38 */ public class SimpleUser { public static final int TYPE = 1; private int id; private String name; public int getId(){ return id; } public void setId(int id) { this.id = id; } public String getName(){ return name; } public void setName(String name){ this.name = name; } }
魔数
固定为0xCAFEBABE(James Gosling 定义),4 个字节(CAFEBABE 8 个 16 进制数,一个 16 进制数需要 4 个二进制数表示,故一共需要 32 个二进制数表示,对应 4 个字节)无符号整数,
class 文件版本
小版本号由 2 个字节无符号整数,之后是大版本号,也用 2 个字节
小版本号 2 个字节为 0x0000
大版本号 2 个字节为 0x0034 对应十进制 52
版本号与平台关系
JDK 版本 | Class 版本号 | 16 进制 |
---|---|---|
1.1 | 45.3 | 00 03 00 2D |
1.2 | 46.0 | 00 00 00 2E |
1.3 | 47.0 | 00 00 00 2F |
1.4 | 48.0 | 00 00 00 30 |
1.5 | 49.0 | 00 00 00 31 |
1.6 | 50.0 | 00 00 00 32 |
1.7 | 51.0 | 00 00 00 33 |
1.8 | 52.0 | 00 00 00 34 |
查看版本号用 javap -verbose SimpleUser
常量池
常量池的数量紧接着大版本号后面 0x0023转成 10 进制为 35,则实际常量池表项有 35-1(常量池 0为空缺项)=34个
常量池表项和 TAG
类型 | TAG |
---|---|
CONSTANT_Utf8 | 1 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_Class | 7 |
CONSTANT_String | 8 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_NameAndType | 12 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
CONSTANT_UTF8
格式
CONSTANT_Utf8_info { u1 tag; // tag 规定为 1,1 个字节 u2 length; //字符串长度,2 个字节,最大 65535 u1 bytes[length]; //字符串具体内容 }
代表整个 CONSTANT_Utf8 包括 tag ,length,bytes[length]
0x01转成十进制 1 ,其中 tag 为 1 代表 CONSTANT_Utf8 类型的常量
0x0032转成十进制50,其中 50 代表实际内容 50 个长度的字符串
0x4C63....3B 代表 50 个长度字符串的具体内容,从图中可知该内容为 Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser; 一共 50 个长度
其中 0x63转成 10 进制为 99 代表字符串c
CONSTANT_Class
表示类的信息,一般只有 2 个,当前类名和object类
结构
CONSTANT_Class_info{ u1 tag; // 固定为 7 u2 name_index; //常量池(CONSTANT_Utf8)的索引,2个字节 }
类的名字在索引值为 33 的项
索引值 33 的项为 com/mousycoder/mycode/thinking_in_jvm/SimpleUser
CONSTANT_Integer
结构
CONSTANT_Integer{ u1 tag; //1个字节无符号整数 u4 bytes; // 4个字节无符号整数 }
0x03 代表 tag = 3 代表 integer ,后面 4 个字节 00 00 00 01 代表实际内容 1,其实对应代码 public static final int TYPE = 1;
CONSTANT_String
结构
CONSTANT_String_info{ u1 tag; //其中 tag 为 8 u2 string_index; // 2 个字节无符号整数指向常量池的索引,表示该字符串对应的 UTF8 内容,最大 oxFF FF = 65535个常量,也是常量池的索引长度最大为 65535,和前面常量池的个数也用u2保持一致。 }
CONSTANT_Integer_info
结构
CONSTANT_Integer_info{ u1 tag; u4 bytes; //4个字节的int }
CONSTANT_Float_info
结构
CONSTANT_Float_info{ u1 tag; u4 tag; // 刚好 float 4 个字节 }
CONSTANT_Long_info
结构
CONSTANT_Long_info{ u1 tag; u4 high_bytes; u4 low_bytes; // 刚好一个 long 是 8 个字节 }
CONSTANT_Double_info
结构
CONSTANT_Double_info{ u1 tag; u4 high_bytes; u4 low_bytes; // 刚好一个 double 是 8 个字节 }
CONSTANT_NameAndType
用于描述字段和方法,个数等于字段和方法的总和
结构
CONSTANT_NameAndType_info{ u1 tag; // tag 为 12 u2 name_index; // 名字(方法、字段)所在常量池的索引 u2 descriptor_index; // 描述信息(方法描述符、字段描述符)所在常量池的索引 }
名字所在常量池的索引为 10,10 对应的字符串信息 id,描述信息所在的常量池索引为 7,7 对应的字符串信息为 I,则表明是一个名称为 id的 int表项,对应 private int id;
descriptor_index 说明
字符串 | 类型 |
---|---|
B | byte |
D | double |
I | int |
S | short |
V | void |
[ | 数组 |
C | char |
F | float |
J | long |
Z | boolean |
L: | 对象 |
V | void方法 |
(Ljava/lang/String;)V 表示一个接受一个String参数并且返回void的方法
CONSTANT_Methodref
表示一个类的方法
结构
CONSTANT_Methodref_info { u1 tag; // 固定值为 10 u2 class_index; //指向常量池中CONSTANT_Class对象 u2 name_and_type_index; // 指向常量池中的 CONSTANT_NameAndType对象 }
Methodref 关系图
解析:
class信息在索引 5 上找,由于这个解析器下标是从 0 开始的,所以找下标为 4 的
下标为 4 的 tag 为 7 代表class,名字在索引为 34 处
得到class的名字为 java/lang/Object;
type信息在索引 30 处找
30 处的 tag 为 12 代表NameAndType ,其中 name 在索引 13 处找,descriptor在索引 14 处找
name 为 <init>
descriptor 为()V
结论:代表 java/lang/Object."<init>":()V 表示Object类方法名为<init>,入参为空,返回值为空的方法,这个就是object类的构造函数,在对象实例化的时候被调用,java在编译的时候,会为每一个class 文件生成一个object类的<init>方法
CONSTANT_Fieldref_info
表示一个类的字段
结构
CONSTANT_Fieldref_info{ u1 tag; // 固定值为 9 u2 class_index;//指向常量池中CONSTANT_Class对象 u2 name_and_type_index; // 指向常量池中的 CONSTANT_NameAndType对象 }
class_index 为 4,对应常量池索引为 3 的class_info
class_info 的索引为33 对应名字为 com/mousycoder/mycode/thinking_in_jvm/SimpleUser
name_and_type_index 对应索引值为 30 的 nameAndType
值为 id ,类型为I,合起来代表 com/mousycoder/mycode/thinking_in_jvm/SimpleUser.id:I
CONSTANT_InterfaceMethodref
表示一个接口方法
结构
CONSTANT_InterfaceMethodref_info{ u1 tag; u2 class_index; u2 name_and_type_index; }
CONSTANT_MethodType
结构
CONSTANT_MethodType_info{ u1 tag; // tag 固定为 16 u2 descriptor_index; }
CONSTANT_MethodHandle
表示一个方法句柄
结构
CONSTANT_MethodHandle_info{ u1 tag; // tag 固定为 15 u1 reference_kind; // 方法句柄类型 u2 reference_index; //常量池索引 }
CONSTANT_InvokeDynamic_info
结构
CONSTANT_InvokeDynamic_info{ u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; }
class 访问标记
u2 access_flags; // 访问标志
类的 access flag含义
名称 | 数值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | 表示public类(可以在包外访问) |
ACC_FINAL | 0x0010 | 是否为final类(final类不可继承) |
ACC_SUPER | 0x0020 | 使用增强的方法调用父类方法 |
ACC_INTERFACE | 0x0200 | 是否为接口 |
ACC_ABSTRACT | 0x0400 | 是否是抽象类 |
ACC_SYNTHETIC | 0x1000 | 由编译器产生的类,没有源码对应 |
ACC_ANNOTATION | 0x2000 | 是否是注释 |
ACC_ENUM | 0x4000 | 是否是枚举 |
0x0021代表 0x0001 | 0x0020 等价于 ACC_PUBLIC | ACC_SUPER
当前类
u2 this_class ; // 对应常量池class_info
结果为 com/mousycoder/mycode/thinking_in_jvm/SimpleUser
父类
u2 super_class; // 对应常量池 class_info , java 只有一个父类,所以这里只保存一个
结果为 java/lang/Object
接口个数
u2 interfaces_count; //接口个数
接口列表
u2 interfaces[interfaces_count]; //具体接口列表
字段个数
u2 fields_count; //字段个数
个数为 3 个,其实对应 int TYPE,int id,String name
字段
结构
field_info { u2 access_flags; //字段访问标志,见 access flag u2 name_index; //字段名称,对应常量池索引 u2 descriptor_index; //字段类型,对应常量池索引 u2 attributes_count; attribute_info attibutes[attributes_count]; } attribute_info{ u2 attribute_name_index; // 属性名称,指向常量池CONSTANT_Utf8,并且这个值为ConstantValue u4 atrribute_length;// 属性剩余长度,对于常量而言,这个值恒为 2 u2 constantvalue_index; // 类型,对应常量池的索引 }
字段的 access flag 描述
名称 | 数值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x00001 | public字段 |
ACC_PRIVATE | 0x0002 | private字段 |
ACC_PROTECTED | 0x0004 | protected字段 |
ACC_STATIC | 0x0008 | 静态字段 |
ACC_FINAL | 0x0010 | 是否为final字段 |
ACC_VOLATILE | 0x0040 | 是否为volatile |
ACC_TRANSIENT | 0x0080 | 是否为瞬间字段,表示在持久化读写时,忽略该字段 |
ACC_SYNTHETIC | 0x1000 | 由编译器产生,没有源码对应 |
ACC_ENUM | 0x4000 | 是否为枚举 |
常量数据类型与常量池类型关系
字段类型 | 常量池表项类型 |
---|---|
long | CONSTANT_Long |
float | CONSTANT_Float |
double | CONSTANT_Double |
int,short,char,byte,boolean | CONSTANT_Integer |
String | CONSTANT_String |
0x0019 对应,ACC_PUBLIC_ACC_STATIC|ACC_FINAL
0x0006代表常量池索引 5,值为TYPE
descriptor_index 为 7 代表 I
代表只有 1 个属性值
代表该字段的属性为ConstantValue
代表剩余字段长度为 2,就是后面 2 个字节代表属性的所有内容 0x0009
代表为一个CONSTANT_Integer 并且值为 1
总结代表 public static final int TYPE = 1
方法个数
u2 methods_count ; // 方法个数
代表有 5 个方法
方法信息
结构
method_info { u2 access_flags;//方法访问标记 u2 name_index; u2 descriptor_index; u2 attributes_count; attributes_info attributes[attributes_count]; } attribute_info{ u2 attribute_name_index; // 名称 u4 attribute_length; // 剩余长度 u2 max_stack;// 操作数栈 u2 max_locals;// 局部变量最大值 u4 code_length;//字节码长度 code[code_length];//字节码具体内容 u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } u2 exception_table_length;// 异常表长度 u2 attributes_count;//属性长度 { u2 attribute_name_index; //名称 u4 attribute_length; //剩余长度 u2 line_number_table_length; 具体内容 { u2 start_pc; //字节码偏移量 u2 line_number;//字节码行号 } } { u2 attribute_name_index; //对应常量表 LocalVariableTable u4 attribute_length;// u2 local_variable_table_length; { u2 start_pc; // 局部变量的开始位置(start_pc),结束位置(start_pc+length) u2 length;//长度 u2 name_index; //常量池中索引位置 u2 descriptor_index; //描述符常量池中的索引 u2 index; //栈帧的局部变量表的槽位 } local_variable_table[local_variable_table_length]; } }
方法访问标记取值
标记名称 | 值 | 作用 |
---|---|---|
ACC_PUBLIC | 0x0001 | public方法 |
ACC_PRIVATE | 0x0002 | private方法 |
ACC_PROTECTED | 0x0004 | protected方法 |
ACC_STATIC | 0x0008 | 静态方法 |
ACC_FINAL | 0x0010 | final方法 |
ACC_SYNCHRONIZED | 0x0020 | synchroinized方法 |
ACC_BRIDGE | 0x0040 | 编辑器产生的桥接方法 |
ACC_VARARGS | 0x0080 | 可变参数的方法 |
ACC_NATIVE | 0x0100 | native本地方法 |
ACC_ABSTRACT | 0x0400 | 抽象方法 |
ACC_STRICT | 0x0800 | 浮点模式为 FP-strict |
ACC_SYNTHETIC | 0x1000 | 编译器产生的方法,没有源码 |
方法属性表
属性 | 作用 |
---|---|
ConstantValue | 字段常量 |
Code | 方法的字节码 |
StackMapTable | Code 属性的描述,用于字节码变量类型验证 |
Exceptions | 方法的异常信息 |
SourceFile | 类文件的属性 |
LineNumberTable | Code属性的描述属性,描述行号和字节码的对应关系 |
LocalVariableTable | Code 属性的描述属性,描述函数局部变量 |
BootstrapMethods | 类文件的描述属性,存放类的引导方法,用于 invokeDynamic |
StackMapTable | Code属性的描述属性,用于字节码类型校验 |
access_flags 为 0x0001 代表 public 方法
name_index 为 26 代表setName
descriptor_index为 27 代表 (Ljava/lang/String;)V 代表方法的入参是String,返回值为Void
attrubute_name_index 为 15 ,找到常量池索引为 14 的,值为Code 表示方法的字节码
attribute_length 为 62 代表 剩余长度为 62,一共 62 个字符
max_stack 为 2 代表操作数栈最大深度
max_locals 代表局部变量表最大值为 2
code_length 为 6,代表数组长度为 6,struct code 代表具体字节码内容,可以看到是 aload_0 ,aload_1,putfield com/mousycoder/mycode/thinking_in_jvm/SimpleUser.nameLjava/lang/String;,Return
exception_table_length 为 0 代表没有异常表
attribute_name_index 为 16,代表常量池中索引获得值为LineNumberTable,剩余长度为 10,line_number_table_length为 2,代表 2 个line_number_table ,start_pc 为字节码偏移量,line_number为行号 30
setName方法里变量名字有 2 个,一个是 this,一个是name
属性
结构
attribute_info { u2 attribute_name_index; //属性名 u4 attribute_length; 属性长度 u2 sourcefile_index; //属性文件 }
总结
警告: 二进制文件SimpleUser包含com.mousycoder.mycode.thinking_in_jvm.SimpleUser Classfile /Users/mousycoder/My/code/mycode/target/classes/com/mousycoder/mycode/thinking_in_jvm/SimpleUser.class Last modified 2019-8-23; size 818 bytes MD5 checksum 20de23f9dc93bcf724f584ead999f846 Compiled from "SimpleUser.java" public class com.mousycoder.mycode.thinking_in_jvm.SimpleUser SourceFile: "SimpleUser.java" minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#30 // java/lang/Object."<init>":()V #2 = Fieldref #4.#31 // com/mousycoder/mycode/thinking_in_jvm/SimpleUser.id:I #3 = Fieldref #4.#32 // com/mousycoder/mycode/thinking_in_jvm/SimpleUser.name:Ljava/lang/String; #4 = Class #33 // com/mousycoder/mycode/thinking_in_jvm/SimpleUser #5 = Class #34 // java/lang/Object #6 = Utf8 TYPE #7 = Utf8 I #8 = Utf8 ConstantValue #9 = Integer 1 #10 = Utf8 id #11 = Utf8 name #12 = Utf8 Ljava/lang/String; #13 = Utf8 <init> #14 = Utf8 ()V #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 LocalVariableTable #18 = Utf8 this #19 = Utf8 Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser; #20 = Utf8 getId #21 = Utf8 ()I #22 = Utf8 setId #23 = Utf8 (I)V #24 = Utf8 getName #25 = Utf8 ()Ljava/lang/String; #26 = Utf8 setName #27 = Utf8 (Ljava/lang/String;)V #28 = Utf8 SourceFile #29 = Utf8 SimpleUser.java #30 = NameAndType #13:#14 // "<init>":()V #31 = NameAndType #10:#7 // id:I #32 = NameAndType #11:#12 // name:Ljava/lang/String; #33 = Utf8 com/mousycoder/mycode/thinking_in_jvm/SimpleUser #34 = Utf8 java/lang/Object { public static final int TYPE; descriptor: I flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 1 public com.mousycoder.mycode.thinking_in_jvm.SimpleUser(); 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 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser; public int getId(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field id:I 4: ireturn LineNumberTable: line 18: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser; public void setId(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: iload_1 2: putfield #2 // Field id:I 5: return LineNumberTable: line 22: 0 line 23: 5 LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser; 0 6 1 id I public java.lang.String getName(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #3 // Field name:Ljava/lang/String; 4: areturn LineNumberTable: line 26: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser; public void setName(java.lang.String); descriptor: (Ljava/lang/String;)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: putfield #3 // Field name:Ljava/lang/String; 5: return LineNumberTable: line 30: 0 line 31: 5 LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/mousycoder/mycode/thinking_in_jvm/SimpleUser; 0 6 1 name Ljava/lang/String; }
一共 818 个bytes(818 个 xx)