一个类从加载到使用,经历以下过程:
1. 类加载器和双亲委派机制
有个多种类加载器:
- 启动类加载器 Bootstrap ClassLoader:
- 负责加载机器上 Java 安装路径下的 “lib“ 目录下的核心类库,去支撑 java 应用的运行;
- 扩展类加载器 Extension ClassLoader:
- 负责加载机器上 Java 安装路径下的 “lib\ext“ 目录下的类,去支撑 java 应用的运行;
- 应用程序类加载器 Application ClassLoarder:
- 负责加载 “ClassPath” 环境变量所指定的路径中的类,就是将你写好的那些类加载到内存里;
自定义类加载器:
- 可以根据自己的需求加载类,例如编译时加密 .class 字节码,通过自定义类加载器加载你的类,并在这个过程中解密字节码;
双亲委派机制
- 就是假设你的应用程序类加载器需要加载一个类,他首先会委派给自己的父类加载器去加载,最终传导到顶层的类加载器去加载,但是如果父类加载器在自己负责加载的范围内,没找到这个类,那么就会下推加载权利给自己的子类加载器。
- 总结:先找父亲去加载,不行的话再由儿子来加载
2. 类加载从触发时机到初始化的过程
- 加载
- 首先代码中包含 “main()” 的主类一定会在 JVM 进程启动后被加载到内存,开始执行 main() 中的代码;
- 遇到代码中使用到别的类,就会从对应的 .class 字节码文件加载对应的类到内存中;
- 验证
- 把“.class”加载到内存里之后,必须先验证一下,校验他必须完全符合 JVM 规范,后续才能交给 JVM 来运行;
- 准备
- 为类分配一定的内存空间
- 给类里面的类变量(也就是 static 修饰的变量)分配内存空间,并给一个默认的初始值,如
ReplicaManager.flushInterval
给个默认值 0;
- 解析
- 实际上是把符号引用替换为直接引用的过程,其实这个部分的内容很复杂,涉及到 JVM 的底层;
- 初始化
- 执行我们的类初始化的代码了
- flushInterval 这个类变量,正式通过
Configuration.getInt(...)
这段代码获取一个值; - static 静态代码块,调用
loadReplicaFromDish()
;
- flushInterval 这个类变量,正式通过
- 什么时候会初始化一个类?
- 比如“new ReplicaManager()”来实例化类的对象了,此时就会触发类的加载到初始化的全过程,把这个类准备好,然后再实例化一个对象出来;
- 包含“main()”方法的主类,必须是立马初始化的;
- 如果初始化一个类的时候,发现他的父类还没初始化,那么必须先初始化他的父类;
- 执行我们的类初始化的代码了
3. 类加载过程示例
根据以下代码进行分析类加载机制:
public class Kafka {
public static void main(){
ReplicaManager replicaManager = new ReplicaManager();
}
}
public class ReplicaManager extends AbstractDataManager {
public static int flushInterval = Configuration.getInt("replica.flush.interval");
public static Map<String, Replica> replicas;
static {
loadReplicaFromDish();
}
public static void loadReplicaFromDish(){
this.replicas = new HashMap<String, Replica>();
}
}