JVM之Classfile文件解析
文章目录
1.魔数
我们在学习JVM时通常会从class文件入手。
那么魔数就是我们认识的第一个东西。
每个Class文件的头4个字节称为魔数(Magic Number)
唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件。
Class文件魔数的值为0xCAFEBABE。如果一个文件不是以0xCAFEBABE开头,那它就肯定不是Java class文件。
很多的文件存储标准中都使用魔数来识别文件的身份。 譬如图片格式.gif 或 jpeg等在文件的头部都存有魔数,使用魔数而不是文件的扩展名称来判断 ,这种情况是处于安全的考虑。
2.class文件版本号
紧挨着魔术的4个字节表示class的文件的版本号 版本号:
1.次版本号 --minor_version 前2个字节用于表示次版本号 例如:00 00
2.主版本号 --major_version 后2个字节用于表示主版本号 例如: 00 34
Java的版本号是从45开始的。如果class的版本号超过虚拟机的版本 会被拒绝执行。
JDK1.2 ----0X002E 46
JDK1.3 ----0X002F 47
JDK1.4 ----0X0030 48
JDK1.5 ----0X0031 49
JDK1.6 ----0X0032 50
JDK1.7 ----0X0033 51
JDK1.8 ----0X0034 52
3. constant_pool_count(常量池个数)
版本号后面是常量池的个数,占两个字节。
constant_pool_count 从1开始计数的。 class文件结构中只有常量池的容量计数是从1开始的。第0项腾出来满足后面某些指向常量池的索引值的数据,在特定的情况下需要表达“不引用任何一个常量池项目” 把索引值的第0项留给JVM自己用。
4. constant_pool(常量池)
长度为 constant_pool_count-1
常量池详细解析常量类型总共有18个编号的常量类型。
constant_pool的具体内容:索引,标志tag,类型
1.编号1: CONSTANT_UTF8_INFO
Tag1 ------占用一个空间字节
Length: utf-8字符串占用的字节数
Bytes 长度为length字符串
用于表示utf-8的编码的字符串
3.编号3 CONSTANT_integer_info
Tag3
Bytes 4个字节 Big_Endian(高位在前) 存储int类型的值
4.编号4 CONSTANT_float_info
Tag4
Bytes 4个字节 Big_Endian(高位在前) 存储float类型的值
5.编号5 CONSTANT_long_info
Tag5
Bytes 8个字节 Big_Endian(高位在前) 存储long类型的值
6.编号6 CONSTANT_double_info
Tag6
Bytes 8个字节 Big_Endian(高位在前) 存储double类型的值
7.编号7 CONSTANT_Class_info
Tag7
Index 2个字节 指向类的全限定名的项的索引
类和接口符号引用
8.编号8 CONSTANT_String_info
Tag8
Index 2个字节 指向字符串的字面量的索引
9.编号9 CONSTANT_Fieldref_info
Tag9
Index 2个字节 指向声明字段的类或接口的描述符 CONSTANT_Class_info的索引项
Index 2个字节 指向字段描述符CONSTANT_NameAndType的索引项
字段的符号引用
10.编号10 CONSTANT_Methodref_info
Tag10
Index 2个字节 指向声明字段的类或接口的描述符 CONSTANT_Class_info的索引项
Index 2个字节 指向字段描述符CONSTANT_NameAndType的索引项
类中方法的符号引用
11.编号11 CONSTANT_InterfaceMethodref_info
Tag11
Index 2个字节 指向声明字段的类或接口的描述符 CONSTANT_Class_info的索引项
Index 2个字节 指向字段描述符CONSTANT_NameAndType的索引项
接口中方法的符号引用
12.编号12 CONSTANT_NameAndType
Tag12
Index 2个字节 指向该字段或方法名称常量项的索引
Index 2个字节 指向该字段或方法描述符常量项的索引
字段或方法的符号引用
15.编号15 CONSTANT_MethodHandler_info
Tag15
Reference_kind 1个字节 1-9之间的一个值 决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
Reference_index 2个字节 对常量池的有效索引。
表示方法句柄
16.编号16 CONSTANT_MethodType_info
Tag16
Descriptor_index 2个字节 指向UTF8_info 结构表示的方法描述符
18.编号18 CONSTANT_InvokeDynamic_info
Tag18
Bootstrap_method_attr_index: 2个字节 当前class文件中引导方法表的bootstrap_methods[] 数组的有效索引
Name_and_type_index: 2个字节 指向NameAndType_info 表示方法名和方法描述符。
表示动态方法的调用点。
5.access_flag
用于表示对该类或接口的访问权限以及该类或接口的属性
6.Method属性
Method属性包含三个字段值
名称access_flag 类型u2 数量1个attributes_count 1
名称 name_index 类型u2 数量1个 attribute_info attributes ----attributes_count
名称 descriptior_index 类型u2 数量1个
descriptior_index
参数列表(参数类型) 后-返回值
void m() 等同于 ()V
String toString() ->()Ljava/lang/String;
Long pos(int[] arr1,int arr2,long length) ->([IIJ)J
7.Fields属性
名称access_flag 类型u2 数量1个attributes_count 1
同method属性
名称 name_index 类型u2 数量1个 attribute_info attributes ----attributes_count
名称 descriptior_index 类型u2 数量1个
同method属性值。
关于attributes_count 附加属性的数量
关于 attributes 附加属性
第9个常量池LineNumberTable 是在后面要使用的常量的名字,在code当中被引用到。
8.双亲委派
Class文件通过自定义的classloader进行加载,如果他没有加载,那么则委托它的父加载器appclassloader 加载, appclassloader 判断是否为本地加载 如果有则直接加载,如果没有则继续向上委托,直到顶层的加载器bootstrapClassLoader,但是当顶层的加载器,也没有加载,就会向下委托,当所有的下级加载器都没有加载那么则抛出异常 classNotFound 异常,如果下级加载器能够加载,那么就由下级加载器进行加载。
双亲:指的有一个从子到父的过程 又有一从父到子的过程
委派:自己不想做的事情 委托别人去完成
向上委派的时候 父加载器都是到 Cache中取寻找
可以把这个缓存理解成是一个list或者是一个数组。
面试题 为什么要去使用双亲委派?
\1. 防止加载同一个class文件,保证数据的安全
\2. 保证核心的class文件不被篡改,即使被篡改了也不会加载,即使被加载也不会是同一个class对象 为了保证class的执行安全。
这部分代码是被写死的。
9.如何打破双亲委派
去重写Classloader中的loadClass方法 而不是findClass方法 这个时候就能够打破双亲委派的机制。
什么时候需要打破需要去打破双亲委派的机制:
在JDK1.2版本之前 要自定义classloader的话 必须要重写loadClass方法
在一个线程中设定自己的线程的上下文的加载器对象 ThreadContextClassLoader 可以实现基础调用实现类的代码, 通过thread.setContextClassLoader 来设定。
模块化的 热部署 热启动
像osgi和tomcat 都有自己的模块指定classloader,可以加载同一个类库的不同版本的对象,目前这个方式用的比较多。
Tomcat中 Webapplication 对象是可以存在多个的,有两个Webapplication 被加载,但是他们的版本不同,这种情况下可以打破双亲委派的。