一个类从加载到使用,经历以下过程:
image.png

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()
    • 什么时候会初始化一个类?
      • 比如“new ReplicaManager()”来实例化类的对象了,此时就会触发类的加载到初始化的全过程,把这个类准备好,然后再实例化一个对象出来;
      • 包含“main()”方法的主类,必须是立马初始化的;
      • 如果初始化一个类的时候,发现他的父类还没初始化,那么必须先初始化他的父类;

3. 类加载过程示例

根据以下代码进行分析类加载机制:

  1. public class Kafka {
  2. public static void main(){
  3. ReplicaManager replicaManager = new ReplicaManager();
  4. }
  5. }
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>();
    }
}

image.png