.class文件
引言
相信作为拥有多年开发经验的你来说,对于.class文件并不陌生,但是你真的认真分析过他里面的规则和逻辑吗?今天就带着你来看看.class文件到底是什么个东西.
1. 初体验
我们可以用javap -v 将.class文件编译成更为可读的助记符,例如:
(请忽略我的文件目录,没啥关系跟这个代码)
javap -v xxx.class
D:\learn\springboot-01-cache\target\classes\com\chy\cache\java>javap -v ByteCode.class
Classfile /D:/learn/springboot-01-cache/target/classes/com/chy/cache/java/ByteCode.class
Last modified 2020-6-22; size 579 bytes
MD5 checksum 984bc023b1033c3fa24dc812ed1c12e6
Compiled from "ByteCode.java"
public class com.chy.cache.java.ByteCode
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#22 // com/chy/cache/java/ByteCode.userName:Ljava/lang/String;
#3 = Class #23 // com/chy/cache/java/ByteCode
#4 = Class #24 // java/lang/Object
#5 = Utf8 userName
#6 = Utf8 Ljava/lang/String;
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/chy/cache/java/ByteCode;
#14 = Utf8 getUserName
#15 = Utf8 ()Ljava/lang/String;
#16 = Utf8 setUserName
#17 = Utf8 (Ljava/lang/String;)V
#18 = Utf8 MethodParameters
#19 = Utf8 SourceFile
#20 = Utf8 ByteCode.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = NameAndType #5:#6 // userName:Ljava/lang/String;
#23 = Utf8 com/chy/cache/java/ByteCode
#24 = Utf8 java/lang/Object
{
public com.chy.cache.java.ByteCode();
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 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/chy/cache/java/ByteCode;
public java.lang.String getUserName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field userName:Ljava/lang/String;
4: areturn
LineNumberTable:
line 10: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/chy/cache/java/ByteCode;
public void setUserName(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 #2 // Field userName:Ljava/lang/String;
5: return
LineNumberTable:
line 14: 0
line 15: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/chy/cache/java/ByteCode;
0 6 1 userName Ljava/lang/String;
MethodParameters:
Name Flags
userName
}
SourceFile: "ByteCode.java"
这个就是我们更加熟悉的jvm助记符,但是.class文件还有他的另一张面孔,那就是16进制的文件:
CA FE BA BE 00 00 00 34 00 19 0A 00 04 00 15 09
00 03 00 16 07 00 17 07 00 18 01 00 08 75 73 65
72 4E 61 6D 65 01 00 12 4C 6A 61 76 61 2F 6C 61
6E 67 2F 53 74 72 69 6E 67 3B 01 00 06 3C 69 6E
69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65
01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62
6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62
6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00
23 4C 63 6F 6D 2F 63 68 79 2F 63 61 63 68 65 2F
6A 61 76 61 2F 54 75 6C 69 6E 67 42 79 74 65 43
6F 64 65 3B 01 00 0B 67 65 74 55 73 65 72 4E 61
6D 65 01 00 14 28 29 4C 6A 61 76 61 2F 6C 61 6E
67 2F 53 74 72 69 6E 67 3B 01 00 0B 73 65 74 55
73 65 72 4E 61 6D 65 01 00 15 28 4C 6A 61 76 61
2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 01
00 10 4D 65 74 68 6F 64 50 61 72 61 6D 65 74 65
72 73 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01
00 13 54 75 6C 69 6E 67 42 79 74 65 43 6F 64 65
2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01
00 21 63 6F 6D 2F 63 68 79 2F 63 61 63 68 65 2F
6A 61 76 61 2F 54 75 6C 69 6E 67 42 79 74 65 43
6F 64 65 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F
4F 62 6A 65 63 74 00 21 00 03 00 04 00 00 00 01
00 02 00 05 00 06 00 00 00 03 00 01 00 07 00 08
00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05
2A B7 00 01 B1 00 00 00 02 00 0A 00 00 00 06 00
01 00 00 00 06 00 0B 00 00 00 0C 00 01 00 00 00
05 00 0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00
09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00
02 B0 00 00 00 02 00 0A 00 00 00 06 00 01 00 00
00 0C 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C
00 0D 00 00 00 01 00 10 00 11 00 02 00 09 00 00
00 3E 00 02 00 02 00 00 00 06 2A 2B B5 00 02 B1
00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 10
00 05 00 11 00 0B 00 00 00 16 00 02 00 00 00 06
00 0C 00 0D 00 00 00 00 00 06 00 05 00 06 00 01
00 12 00 00 00 05 01 00 05 00 00 00 01 00 13 00
00 00 02 00 14
这个16进制的.class文件和咱们的jvm助记符的每一行指令都是一一对应的,那么他们有什么规律呢?
2.逐行翻译.class文件
开篇提到了.class文件其实是16进制表示的,那么一个16进制的数字,例如C,他其实是4bit,
那么就可推断出,CA,其实是一个Byte,因为1Byte = 8 bit.
所以.class文件开头的CA FA BA BE一共占了4个字节,那么他代表什么含义呢?
其实他叫Magic Number(魔数),他是一个固定值,他也相当于一个Logo,也是一个标识.
紧接着后面的00 00 代表了次版本号,也就是此版本是0,占了两个字节.
00 34是主版本号,仍然占两个字节,00 34 转换成10进制是52,而52对应的jdk版本就是1.8,可以推出,51是jdk几呢,没错就是1.7!
00 19这个代表了常量池常量的个数,也就是25个,
这个怎么只有24个呢,其实第一位是null.被jvm占用了.
接下来的所有16进制数值,就代表了常量池里的每行代码的含义了.
0A 代表了常量池的标记位,A = 10,他是代表着这个一个描述方法的常量,method_info类型
这五个字节代表了描述方法的常量.
u1表示一个字节是10,u2表示两个字节的索引位,也就是常量池的索引,根据.class文件可以看出索引位是00 04,表示的是哪个类
00 15指向了21,表示了名称和类型.
也就是Object类的构造方法,返回类型是void.
紧接着是09,
第二个常量他表示了一个字段的信息,前一位是标记位,后一位索引位,最后一位表示了名字和类型,占了5个字节
03,指向了3号位置,16指向了22号位置,
ByteCode类的userName方法名,返回值为String.
第三个常量为,
07标志位,表示了class信息,占了三个字节,u2是他的索引位.
17,是23指向了ByteCode
第四个常量,又是07
跟上面是一样的,占三个字节,找到18 = 24的索引位置,它的父类Object
第五个常量,01标记位,表示了字面量utf8结果体,u2两个字节表示字符长度08,也就是表示是8个长度,紧接着往后面数8个长度位
75 73 65 72 4E 61 6D 65
第六个常量,又是01,字面量,12是长度位,往后面数18个字节,表示String类型
不知道找没找到点感觉,加快速度,第七个常量,紧接着后面又是01,这个是标志位,跟上面同理,01后面的00 06是长度为,向后数6个字节.
第八个常量又是01,后面00 03长度位,数三个字节,
第九个常量,又是01,吐了,后面长度是04,数四个字节,
他代表字节码表.
第十个常量,又是01,00 0F 数15个字节,
代表行号表,这里有个点就是,大家有没有想过,为什么抛异常的时候,他可以知道,是哪行抛出的异常.其实这里的行号表,和我们的java源码是一一对应的.
第十一个常量又是01和上面一样,数18位,
代表局部变量表(本地变量表),每个方法都有一个LocalVariableTable,
stack=1是栈的深度,locals=1是局部变量,也就是说局部变量表在编译时期就确定了.
从截图可以看到ByteCode()是他的构造方法,但是从图上看,他就一个局部变量.其实是this,他在编译时会作为隐式的参数放在局部变量表的第一个位置.
第十二个常量
第十三个常量,
他描述的是一个全限定名.
…
相信大家都会了吧.
常量池分析完了,接着分析下面的访问修饰符.
00 21,
紧接着下面就是this class name
00 03指向了常量池3号位置
常量池是不是很有用,他就相当于是一个资源池,可以重复的引用.
下面是父类的名称,
00 04 ,指向了常量池的第四位
表示实现了0个接口,那么有的小伙伴可能会问了,那么这个接口你这么说也会有上限啊?答案是肯定的 FFFF就是他的上限,这个是个细节哦嘻嘻嘻.
接下来是字段的个数00 01,有一个字段. userName.接下来是字段表
00 02表示字段表中的类型
**00 05
接下来是描述符的索引00 06 **
接下来是属性表的个数.**00 00 **表示没有.
**00 03 **表示方法的个数,
get set外加构造.
紧接着后面又是方法的方发表
“name_index”: “u2(00 07)->desc:表示方法名称的索引指向常量池第七个位置#7 表示 再编译时期生成的默认的构造函数”,
“descciptor_index”: “u2(00 08)->desc:表示方法描述索引指向常量池#8 表示:()V 无参数,无返回值”,
“attribute_count”: “u2(00 01)->desc:表示方法的属性个数”,表示只有一个
"Code": {
"attribute_name_index": "u2(00 09)->desc:我们属性的名称指向常量值索引的#9 位置 值为Code",
"attribute_length": "u4(00 00 00 2F)-desc:表示我们的Code属性紧接着下来的47个字节是Code的内容",
"max_stack": "u2(00 01)->desc:表示该方法的最大操作数栈的深度1",
"max_locals": "u2(00 01)->desc:表示该方法的局部变量表的个数为1",
"Code_length": "u4(00 00 00 05)->desc:指令码的长度为5",
"Code[Code_length]": "2A B7 00 01 B1 其中0x002A->对应的字节码注记符是aload_0;0xB7->invokespecial;00 01表示表示是B7指令码操作的对象指向常量池中的#1(java/lang/Object.<init>:()V);B1表示调用方法返回void",
"exception_table_length": "u2(00 00)->表示该方法不抛出异常,故exception_info没有异常信息",
"exception_info": {},
"attribute_count": "u2(00 02)->desc表示code属性表的属性个数为2",
"attribute_info": {
"LineNumberTable": {
"attribute_name_index": "u2(00 0A)当前属性表名称的索引指向我们的常量池#10(LineNumberTable)",
"attribute_length": "u4(00 00 00 06)当前属性表属性的字段占用6个字节是用来描述line_number_info",
"mapping_count": "u2(00 01)->desc:该方法指向的指令码和源码映射的对数 表示一对",
"line_number_infos": {
"line_number_info[0]": {
"start_pc": "u2(00 00)->desc:表示指令码的行数",
"line_number": "u2(00 06)->desc:源码的行号"
}
}
},
"localVariableTable": {
"attribute_name_index": "u2(00 0B)当前属性表名称的索引指向我们的常量池#10(localVariableTable)",
"attribute_length": "u4(00 00 00 0C)当前属性表属性的字段占用12个字节用来描述local_variable_info",
"local_variable_length": "u2(00 01)->desc:表示局部变量的个数1个",
"local_variable_infos": {
"local_variable_info[0]": {
"start_pc": "u2(00 00 )->desc:这个局部变量的生命周期开始的字节码偏移量",
"length:": "u2(00 05)->作用范围覆盖的长度为5",
"name_index": "u2(00 0c)->字段的名称索引指向常量池12的位置 this",
"desc_index": "u2(00 0D)局部变量的描述符号索引->指向#13的位置Lcom/tuling/smlz/jvm/classbyatecode/TulingByteCode;",
"index": "u2(00 00)->desc:index是这个局部变量在栈帧局部变量表中Slot的位置"
}
}
}
}
}