结构
结构体
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)