Java源代码经过编译生成.class文件字节码后再被JVM解释转化为目标机器代码,从而实现一次编写处处,处处运行("Write Once,Run Anywhere")。字节码与平台无关,并且并非只有Java语言编译为字节码文件在虚拟机上运行。java
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中。Class文件只有两种数据类型:无符号数和表。
数组
无符号数属于基本的数据类型,有u1, u2, u4, u8,分别表明1个字节、2个字节、4个字节和8个字节的无符号数
并发
整个Class文件就是一张表,由如下数据项构成:
this
类型 | 名称 | 数量 |
---|---|---|
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 |
mehtod_info | methods(方法) | method_count |
u2 | attributes_count(属性容量) | 1 |
attribute | attributes(属性) | attributes_count |
写个简单实体类,javac编译后,查看其字节码
源码编码
public class Person {
private int age;
public int getAge(){
return age;
}
public static synchronized void work() {
System.out.println("工做");
}
public static void main(String[] args) {
}
}
复制代码
十六进制Class文件spa
根据上述数据项表格咱们按顺序拆分code
魔数站每一个Class文件的头4个字节,其做用未肯定肯定这个文件是否为一个能被虚拟机接受的Class文件 示例中CA FE BA BE为魔数
cdn
魔数后面紧跟着版本号
00 00——次版本号
00 34——主版本号
根据以下:对象
十进制版本号 | 主版本 |
---|---|
jdk1.8 | 52 |
jdk1.7 | 51 |
jdk1.6 | 50 |
jdk1.5 | 49 |
jdk1.4 | 48 |
jdk1.3 | 47 |
jdk1.2 | 46 |
jdk1.1 | 45 |
常量池能够理解为Class文件之中的资源仓库,它是Class文件结构中与其余项目关联最多的数据类型。常量池中主要存放两大类常量:字面量和符号引用.
字面量——接近于Java中的常量概念,eg:final修饰、文本字符串
符号引用——编译原理概念:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符
常量池中的每一项常量都是一个表,每种常量都有本身的结构,14种常量含义:blog
类型 | 标志 | 描述 |
---|---|---|
CONSTANT_utf8_info | 1 | utf-8编码的字符串 |
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_InterfaceMethoderf_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 标识方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
0×0029转十进制为41,表明常量池中有40项常量(容量计数是从1而不是0开始。第0项常量空出来是表达“不引用任何一个常量池项目”)
0A即十进制10,对应表中CONSTANT_Methodref_info,其结构以下:
类型 | 名称 | 描述 |
u1 | tag | 值为10 |
u2 | index | 指向声明方法的类描述符CONSTANT_Class_info的索引项 |
u2 | index | 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 |
00 29 //constant_pool_count(常量池容量)
#一、0A 0007 001A //CONSTANT_Methodref_info,#7,#26
#二、09 0006 001B //CONSTANT_Fieldref_info,#6,#27
#三、09 001C 001D //CONSTANT_Fieldref_info,#28,#29
#四、08 001E //CONSTANT_String_info,#30
#五、0A 001F 0020 //CONSTANT_Methodref_info,#31,#32
#六、07 0021 //CONSTANT_Class_info,#33
#七、07 0022 //CONSTANT_Class_info,#34
#八、01 0003 61 67 65 //CONSTANT_Utf8_info,3个字节,age
#九、01 0001 49 //CONSTANT_Utf8_info,1个字节,I
#十、01 0006 3C 69 6E 69 74 3E //CONSTANT_Utf8_info,6个字节,
#十一、01 0003 28 29 56 //CONSTANT_Utf8_info,3个字节,()V
#十二、01 0004 43 6F 64 65 //CONSTANT_Utf8_info,4个字节,Code
#1三、01 000F 4C 69 6E 65 4E 75 6D 62 //CONSTANT_Utf8_info,15个字节,LineNumberTable
65 72 54 61 62 6C 65
#1四、01 0012 4C 6F 63 61 6C 56 61 72 //CONSTANT_Utf8_info,18个字节,LocalVariableTable
69 61 62 6C 65 54 61 62
6C 65
#1五、01 0004 74 68 69 73 //CONSTANT_Utf8_info,4个字节,this
#1六、01 0008 4C 50 65 72 73 6F 6E 3B //CONSTANT_Utf8_info,8个字节,LPerson;
#1七、01 0006 67 65 74 41 67 65 //CONSTANT_Utf8_info,6个字节,getAge
#1八、01 0003 28 29 49 //CONSTANT_Utf8_info,3个字节,()I
#1九、01 0004 77 6F 72 6B //CONSTANT_Utf8_info,4个字节,work
#20、01 0004 6D 61 69 6E //CONSTANT_Utf8_info,4个字节,main
#2一、01 0016 28 5B 4C 6A 61 76 61 2F //CONSTANT_Utf8_info,22个字节,([Ljava/lang/String;)V
6C 61 6E 67 2F 53 74 72
69 6E 67 3B 29 56
#2二、01 0004 61 72 67 73 //CONSTANT_Utf8_info,4个字节,args
#2三、01 0013 5B 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info,19个字节,[Ljava/lang/String;
61 6E 67 2F 53 74 72 69
6E 67 3B
#2四、01 000A 53 6F 75 72 63 65 46 69 //CONSTANT_Utf8_info,10个字节, SourceFile
6C 65
#2五、01 000B 50 65 72 73 6F 6E 2E 6A //CONSTANT_Utf8_info,11个字节, Person.java
61 76 61
#2六、0C 000A 000B //CONSTANT_NameAndType_info,#10,#11
#2七、0C 0008 0009 //CONSTANT_NameAndType_info,#8,#9
#2八、07 0023 //CONSTANT_Class_info,#35
#2九、0C 0024 0025 //CONSTANT_NameAndType_info,#36,#37
#30、01 0006 E5 B7 A5 E4 BD 9C //CONSTANT_Utf8_info,6个字节,工做
#3一、07 0026 //CONSTANT_Class_info,#38
#3二、0C 0027 0028 //CONSTANT_NameAndType_info,#39,#40
#3三、01 0006 50 65 72 73 6F 6E //CONSTANT_Utf8_info,6个字节,Person
#3四、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info,16个字节,java/lang/Object
67 2F 4F 62 6A 65 63 74
#3五、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info,16个字节, java/lang/System
67 2F 53 79 73 74 65 6D
#3六、01 0003 6F 75 74 //CONSTANT_Utf8_info,3个字节,out
#3七、01 0015 4C 6A 61 76 61 2F 69 6F //CONSTANT_Utf8_info,21个字节, Ljava/io/PrintStream;
2F 50 72 69 6E 74 53 74
72 65 61 6D 3B
#3八、01 0013 6A 61 76 2F 69 6F 2F 50 //CONSTANT_Utf8_info,19个字节,java/io/PrintStream
72 69 6E 74 53 74 72 65
61 6D
#3九、01 0007 70 72 69 6E 74 6C 6E //CONSTANT_Utf8_info,7个字节,println
#40、01 0015 28 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info,21个字节, (Ljava/lang/String;)V
61 6E 67 2F 53 74 72 69
6E 67 3B 29 56
复制代码
也能够java -verbose分析Class文件字节码,获得结果:
Constant pool:
#1 = Methodref #7.#26 // java/lang/Object."":()V
#2 = Fieldref #6.#27 // Person.age:I
#3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #30 // 工做
#5 = Methodref #31.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Class #33 // Person
#7 = Class #34 // java/lang/Object
#8 = Utf8 age
#9 = Utf8 I
#10 = Utf8
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 LPerson;
#17 = Utf8 getAge
#18 = Utf8 ()I
#19 = Utf8 work
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 SourceFile
#25 = Utf8 Person.java
#26 = NameAndType #10:#11 // "":()V
#27 = NameAndType #8:#9 // age:I
#28 = Class #35 // java/lang/System
#29 = NameAndType #36:#37 // out:Ljava/io/PrintStream;
#30 = Utf8 工做
#31 = Class #38 // java/io/PrintStream
#32 = NameAndType #39:#40 // println:(Ljava/lang/String;)V
#33 = Utf8 Person
#34 = Utf8 java/lang/Object
#35 = Utf8 java/lang/System
#36 = Utf8 out
#37 = Utf8 Ljava/io/PrintStream;
#38 = Utf8 java/io/PrintStream
#39 = Utf8 println
#40 = Utf8 (Ljava/lang/String;)V
复制代码
在常量池以后紧接着两个字节表明访问标志,用于识别一些类或者接口层次的访问信息
具体的标志位和含义以下:
名称 | 标志值 | 含义 |
ACC_PUBLIC | 0×0001 | 是否为public |
ACC_FINAL | 0x0010 | 是否为final |
ACC_SUPER | 0x0020 | JDK 1.0.2以后编译出来的类这个标志都为真 |
ACC_INTERFACE | 0x0200 | 是否为一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型 |
ACC_SUPER | 0x0020 | JDK 1.0.2以后编译出来的类这个标志都为真 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并不是由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 是不是注解 |
ACC_ENUM | 0x4000 | 是不是枚举 |
类索引、父类索引与接口索引(指向常量池)都是u2类型的数据,除了java.lang.Object 以外全部的Java类都有父类,没有实现接口计数器为0,本例:
0006 //this_class Person
0007 //super_class java/lang/Object
0000 //没有实现结构故0
复制代码
字段表用于描述类和接口中声明的变量。字段包括类级变量和实例级变量,可是不包括方法中的变量。字段信息:字段的做用域,public/private/protected 实例变量仍是类变量,static 可变性,final 并发可见性, volatile 能否被序列化, transient,字段数据类型(基本类型,对象,数组),字段名称
字段表结构:
类型 | 名称 | 数量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
0001 //fields_count 字段容量即1个字段
0002 //访问标志 private
0008 //常量池第8项,即age
0009 //字段描述符,常量池第9项,即I
0000 //attribute_count
复制代码
Class文件存储格式中对方法的描述与对字段的描述几乎采用彻底一致的方式。
方法表结构:
类型 | 名称 | 数量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
0004 //方法容量,即4个方法:实例构造器、getAge()、work以及main方法 0001 //方法访问标志,public 000A //常量池第10项, 000B //方法描述常量池第11个,()V,没返回值 0001 //attribute_count 000C //常量池第12项,Code属性表,存放方法里的Java代码 0000002F //属性表长度 47 ... 47个字节后复制代码0001 //方法访问标志,public 0011 //常量池第17项,getAge 0012 //方法描述常量池第18个,()I 返回int型 0001 //attribute_count 000C //常量池第12项,Code属性表,存放方法里的Java代码 0000002F //属性表长度 47 ... 47个字节后 0029 // ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED 0×0001|0×0008|0×0020 0013 //常量池第19项,work 000B //方法描述常量池第11个,()V,没返回值 0001 //attribute_count 000C //常量池第12项,Code属性表,存放方法里的Java代码 00000025 //属性表长度 37 ...37个字节后 0009 //ACC_PUBLIC, ACC_STATIC 0×0001|0×0008 0014 //常量池第20项,main 0015 //方法描述常量池第21项,([Ljava/lang/String;)V String数组形参,无返回类型方法 0001 //attribute_count 000C //常量池第12项,Code属性表,存放方法里的Java代码 0000002B //属性表长度 43 复制代码复制代码0001 //方法访问标志,public 0011 //常量池第17项,getAge 0012 //方法描述常量池第18个,()I 返回int型 0001 //attribute_count 000C //常量池第12项,Code属性表,存放方法里的Java代码 0000002F //属性表长度 47 ... 47个字节后 0029 // ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED 0×0001|0×0008|0×0020 0013 //常量池第19项,work 000B //方法描述常量池第11个,()V,没返回值 0001 //attribute_count 000C //常量池第12项,Code属性表,存放方法里的Java代码 00000025 //属性表长度 37 ...37个字节后 0009 //ACC_PUBLIC, ACC_STATIC 0×0001|0×0008 0014 //常量池第20项,main 0015 //方法描述常量池第21项,([Ljava/lang/String;)V String数组形参,无返回类型方法 0001 //attribute_count 000C //常量池第12项,Code属性表,存放方法里的Java代码 0000002B //属性表长度 43 复制代码
对于本例:
000C //常量池第12项,Code属性
0000002F //Code属性表长度 47
0001 //max_stack 操做数栈深度最大值 1
0001 //max_locals 局部变量存储
00000005 //code_length 字节码长度
2A B7 00 01 B1 //字节码指令
0000 //exception_table_length
0002 //attributes_count 2个属性
000D //常量池第13项, LineNumberTable属性
00000006 //LineNumberTable属性表长度
0001 //line_number_table_length
0000 //start_pc 字节码行号
0001 //line_number Java源码行号
000E //常量池第14项,LocalVariableTable属性
0000000C //attribute_length
0001 //local_variable_table_length
0000 //start_pc 这个局部变量的生命周期开始的字节码偏移量
0005 //局部变量做用范围覆盖的长度
000F //name_index 局部变量名称 常量池第15项,this
0010 //descriptor_index 局部变量描述 常量池第16项,LPerson;
0000 //这个局部变量在栈帧局部变量表中Slot的位置
复制代码
000C //常量池第12项,Code属性
0000002F //attribute_length
0001 //max_stack 操做数栈深度最大值 1
0001 //max_locals 局部变量存储
00000005 //code_length 字节码长度
2A B4 00 02 AC //字节码指令
0000 //exception_table_length
0002 //attributes_count 2个属性
000D //常量池第13项, LineNumberTable属性
00000006 //LineNumberTable属性表长度
0001 //line_number_table_length
0000 //start_pc 字节码行号
0006 //line_number Java源码行号
000E //常量池第14项,LocalVariableTable属性
0000000C //attribute_length
0001 //local_variable_table_length
0000 //start_pc 这个局部变量的生命周期开始的字节码偏移量
0005 //局部变量做用范围覆盖的长度
000F //name_index 局部变量名称 常量池第15项,this
0010 //descriptor_index 局部变量描述 常量池第16项,LPerson;
0000 //这个局部变量在栈帧局部变量表中Slot的位置
复制代码
000C //常量池第12项,Code属性
00000025 //attribute_length
0002 //max_stack 操做数栈深度最大值 2
0000 //max_locals 局部变量存储
00000009 //code_length 字节码长度
B2 00 03 12 04 B6 00 05 B1 //字节码指令
0000 //exception_table_length
0001 //attributes_count 1个属性
000D //常量池第13项, LineNumberTable属性
0000000A //LineNumberTable属性表长度
0002 //line_number_table_length 2个line_number_info
0000 //start_pc 字节码行号
000A //line_number Java源码行号
0008 //start_pc 字节码行号
000B //line_number Java源码行号
复制代码
000C //常量池第12项,Code属性
0000002B //attribute_length
0000 //max_stack 操做数栈深度最大值 0
0001 //max_locals 局部变量存储
00000001 //code_length 字节码长度
B1 //字节码指令
0000 //exception_table_length
0002 //attributes_count 2个属性
000D //常量池第13项, LineNumberTable属性
00000006 //LineNumberTable属性表长度
0001 //line_number_table_length 1个line_number_info
0000 //start_pc 字节码行号
000F //line_number Java源码行号
000E //常量池第14项,LocalVariableTable属性
0000000C //attribute_length
0001 //local_variable_table_length
0000 //start_pc 这个局部变量的生命周期开始的字节码偏移量
0001 //局部变量做用范围覆盖的长度
0016 //name_index 局部变量名称 常量池第22项,args
0017 //descriptor_index 局部变量描述 常量池第23项,[Ljava/lang/String;
0000 //这个局部变量在栈帧局部变量表中Slot的位置
复制代码
本篇作了一个小小的尝试,按照数据项表格一一解析,感兴趣的同窗能够读下《深刻理解Java虚拟机》这本圣书。
《深刻理解Java虚拟机》