什么是类加载器?

JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中 ,从而让Java程序能够启动起来。

类加载的过程

1. 加载

该阶段虚拟机需要完成三件事:

  1. 通过一个类的全限定类名获取定义类的二进制字节流
  2. 将字节流所代表的静态存储结构转化为方法区的运行时数据区
  3. 在内存中生成对应该类的 Class 实例,作为方法区这个类的数据访问入口
2. 验证

确保 Class 文件的字节流符合约束。如果虚拟机不检查输入的字节流,可能因为载入错误或恶意企图的字节流而导致系统受攻击。
验证主要包含四个阶段:文件格式验证、元数据验证、字节码验证、符号引用验证。
验证重要带是非必须,如果代码已被反复使用和验证过,在生产环境就可以考虑关闭大部分验证缩短类加载时间。

3. 准备

准备阶段正式为类变量(即被 static 修饰的变量)分配内存并设置零值。
如果是 final static 常量则在这一阶段直接赋予代码中指定的值,而非零值,

4. 解析

虚拟机将常量池内的符号引用替换为直接引用的过程。

  • 符号引用:以一组符号来描述所引用的目标。比如用字符串"java/io/PrintStream" 来表示 PrintStream 类。它和内存布局无关。
  • 直接引用:直接指向目标的指针】相对偏移量或者是一个能间接定位到目标的句柄。这是和内存布局直接相关的。
    解析动作主要针对类或接口、字段、类方法、接口方法等引用进行。这个过程不一定在初始化之前就全部完成,JVM规范允许“懒解析”,即在第一次使用某个符号引用时才去解析它。

    5. 初始化

    这是类加载过程的最后一步,也是真正开始执行程序员编写的 Java 代码的阶段。
    目标:执行<clinit>()方法——该方法是Java编译器自动收集类中所有静态变量的赋值动作静态代码块(static{}) 中的语句合并产生的。
    特性

  • 执行时机:只有当类被“主动使用”时才会出发初始化,比如new 一个对象、访问类的静态字段或静态方法等。
  • 父类优先:如果一个类有父类,那么初始化子类之前,JVM会保证其父类已经被初始化。
  • 线程安全:JVM会保证一个类的<clinit>() 方法在多线程环境中被正确地加锁、同步。即同一个类只会被初始化一次。

    总结
  • 加载:找到.class文件,加载进内存。
  • 验证:检查文件和代码的安全性。
  • 准备:为static变量分配内存并设置零值(final static 常量除外)。
  • 解析:把代码中的符号名(如类名、方法名)换成直接的内存地址。
  • 初始化:执行static变量的赋值和static代码块,完成类的首次启动

类加载器有哪些种类?

  1. 启动类加载器(加载JAVA_HOME/jre/lib目录下的库)
  2. 扩展类加载器(加载JAVA_HOME/jre/lib/ext目录下的库)
  3. 应用类加载器(加载开发者自己编写的Java类)
  4. 自定义类加载器(实现自定义类加载规则)

image.png

什么是双亲委派模型?

当一个类加载器收到加载类的请求时,它不会自己先去尝试加载这个类,而是会把这个请求委派给它的父类加载器去完成。这个过程会一直向上委派,直到到达顶层的启动类加载器(Bootstrap ClassLoader)。只有当父类加载器反馈自己无法完成这个加载请求(即在它的搜索范围内没有找到所需的类)时,子加载器才会自己去尝试加载。

JVM为什么采用双亲委派机制?

  1. 通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。
  2. 为了安全,保障类库API不会被修改。
最后修改:2025 年 07 月 22 日
如果觉得我的文章对你有用,请随意赞赏