类结构和类加载
1. 类文件结构
1 | ClassFile { |
1.1 魔数(amgic)
唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。即进行类型识别。使用魔数而不使用文件拓展名是为了安全考虑。
1 | u4 magic; //Class 文件的标志 |
1.2 版本号(minor_version & major_version)
高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件。
1 | u2 minor_version;//Class 的小版本号 |
1.3 常量池
1 | u2 constant_pool_count;//常量池的数量 |
constant_pool_count
表示常量池的容量,索引从1开始,到他的数量-1.第0项常量表示不引用任何常量,默认为空。
0x0016:表示为十进制是22,表示有21个常量,索引从1-21
主要存放两类常量:字面量和符号引用
每一项常量都是一个表,表开始是u1类型的标志位。
1.4 访问标志
识别类或接口层次的访问消息。
1 | u2 access_flags;//Class 的访问标记 |
1.5 类索引,父类索引和接口索引集合
当前类要设置全限名,所有的都有父类,除了java.lang.Object,接口可以多实现。
1 | u2 this_class;//当前类 |
1.6 字段表集合
1 | u2 fields_count;//Class 文件的字段属性 |
字段表格式:
access_flags
: 字段的作用域(public ,private,protected修饰符),是实例变量还是类变量(static修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。name_index
: 对常量池的引用,表示的字段的名称;descriptor_index
: 对常量池的引用,表示字段和方法的描述符;attributes_count
: 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;attributes[attributes_count]
: 存放具体属性具体内容。
字段表标志位:
1.7 方法表集合
1 | u2 methods_count;//Class 文件的方法数量 |
方法表结构:
方法表标志位:
2. 类加载机制
类的生命周期:
类加载的时机:
- 创建类的实例,也就是
new
一个对象。 - 访问类的静态方法或者静态变量(包含静态变量赋值)。
- 使用
Class.forName()
反射类。 - 子类初始化的时候。
- JVM启动时标明的启动类。
2.1 加载
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个流的静态存储结构转换为方法区运行时数据结构。
- 生成一个Class对象
jvm是懒加载,所以只有使用到类时才会加载,例如调用类的main()方法,new对象等等 ,主类在运行过程中如果使用到其它类,会逐步加载这些类。
2.2 验证
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
2.3 准备
为类中定义的变量(被static
修饰过的变量)分配内存并设置类变量初始值。此阶段不包含实例变量的赋值。
2.4 解析
将符号引用转换为直接引用。
- 符号引用: 描述对象,包括如下三种:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符 - 直接引用:
变量有一个内存地址来标识,如果我们用一个指针指向这个内存地址,这个指针就是直接引用。
等我们需要用到这个变量的时候,就可以直接通过指针指向的地址找到。
而我们在加载类的时候,解析代码并指向内存某个地址,然后将符号引用 obj和这个内存地址进行映射的过程,就是解析这个步骤要做的事,也叫做符号引用转换为直接引用。
2.5 初始化
(1)对类的静态变量初始化为指定的值int initData = 666
(2)执行静态代码块<clinit>()
3. 类加载器
3.1 类加载器的种类
- 启动类加载器(Bootstrap ClassLoader):负责加载Java类的核心类(
<JAVA_HOME>\lib
目录下,能被-Xbootclasspath
参数所指定路径存放的),是用C++代码实现的,无法被java代码直接引用。 - 扩展类加载器(Extensions ClassLoader):负责加载JRE的扩展目录lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为Null。
- 应用程序类加载器(Application ClassLoader): 负责加载用户类路径 classpath 上所有的 jar 包和 .class 文件。
3.2 双亲委派模型
工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的夹杂请求最终都应该传送到顶层的启动类加载器中,只有父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
简而言之:自下而上请求,自上而下加载
优势:
父类加载器成功加载则返回,子类加载器不会再加载,防止了重复加载。
防止核心API库被随意篡改。比如有一个要加载java.lang.Integer类的请求,通过双亲委派进制加载传递到启动类加载器,在在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,可以防止核心API被随意篡改。