JVM-类加载器子系统

JVM——类加载器子系统

类加载器子系统:负责从文件系统/网络中加载class文件

1. 类加载器分类

  • 启动类加载器Bootstrap ClassLoader
  • 扩展类加载器Extension ClassLoader
  • 系统类加载器AppClassLoader
  • 用户自定义加载器

除Bootstrap CLassLoader,其他类加载器都继承于ClassLoader

2. 双亲委派机制

  1. 一个类加载器收到了类加载请求,先把请求一层层向上面的父类加载器委托,直到Bootstrap ClassLoader
  2. 若父类可加载则直接返回,否则向下层子加载器分配任务
  3. 若分配至AppClassLoader也无法加载,抛出ClassNotFound异常

双亲委派机制作用

  • 避免类重复加载
  • 防止核心API被篡改

双亲委派机制

当执行main方法的时候,会出现如下问题:

在这里插入图片描述

可想而知,编译器根据包名一层一层的向上请求,在请求 bootstrap classloader加载器的时候, bootstrap classloader发现自己可以加载java.lang包下的String类,所以对类库中的String进行了加载,但是类库中的String类并没有main方法,所以抛出了上述异常。

判断两个类是否为同一个类

  • 完整类名相同
  • 加载该类的类加载器相同

打破双亲委派机制

自定义ClassLoader并且重写loadClass方法。为什么需要打破双亲委派机制:

在一个 Web 容器(如 Tomcat)中,可能同时部署了两个 Web 应用 A 和 B。

  • 问题:应用 A 需要 Spring 4.0,应用 B 需要 Spring 5.0。
  • 冲突:如果遵循双亲委派,共同的父类加载器加载了其中一个版本的 Spring,另一个应用就无法加载自己需要的版本了。
  • 解决:Tomcat 为每个 Web 应用创建了独立的 WebAppClassLoader,它会打破常规,优先加载应用自身目录下的类(WEB-INF/classes),从而实现不同应用间的类库隔离。

自定义类加载器

继承ClassLoader并且重写findClass方法loadClass 是负责协调委派流程的“管理者”,而 findClass 是负责去具体地方找字节码的“打工人”:

  • loadClass 是类加载的入口方法,它定义了加载的策略(即双亲委派机制)。

    • 默认行为:它会先检查类是否已加载,如果没有,就问父加载器要;父加载器要不到,才调用自己的 findClass
    • 重写它的目的:通常是为了打破双亲委派机制(例如 Tomcat 想要先加载自己的类,而不是先问父类)。
  • findClass 是 JDK 1.2 之后为了不破坏双亲委派而专门设计的扩展点

    • 默认行为:在基类 ClassLoader 中,它直接抛出 ClassNotFoundException
    • 重写它的目的:在遵循双亲委派机制的前提下,告诉加载器如何去非标准路径(如数据库、网络、加密文件)获取类的字节码。

3. 创建对象的过程

1. 类加载检查

虚拟机遇到一条new指令时,先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用的类是否已经被加载、解析、初始化过。如果没有,就需要先执行相应的类加载过程(双亲委派)

2. 分配内存

对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务即把一块确定大小的内存空间从堆中划分出来

分配方式:

  • 指针碰撞:适用于堆内存完整,没有内存碎片的情况。原理就是通过一个指针记录空闲内存与已用内存之间的分界线,分配内存只需移动这个指针即可(Serial、ParNew)
  • 空闲列表:适用于堆内存不完整,有内存碎片的情况。原理就是虚拟机通过维护一个列表,记录哪些内存块是可用的,在分配的时候找一块足够大的内存块分配给对象实例,然后更新列表(CMS)

内存分配并发问题解决方法:

  • CAS + 重试
  • TLAB:先在线程自己的TLAB内分配内存,不够用时才去堆中使用CAS进行分配

3. 初始化0值

4. 进行必要设置

初始化0值完成后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的hash码,对象的GC分代年龄等信息。这些信息存放在对象头中。

另外,根据虚拟机的状态不同,是否启用偏向锁等,对象头会有不同的设置方式。

5. 执行init方法

即执行对象的构造函数

4. 类卸载

卸载类需要满足三个条件:

  • 该类对应的所有实例对象都已被GC,即堆中不存在该类的实例对象
  • 该类对应的class对象没有在其他任何地方被引用,无法在任何地方通过反射访问该类的方法
  • 该类的类加载器实例已被GC

所以,在JVM生命周期内,由JVM自带的类加载器加载的类是不会被卸载的,因为自带的类加载器不会被GC。只有由我们自定义的类加载器所加载的类会被卸载


JVM-类加载器子系统
http://example.com/2025/05/16/JVM-类加载器子系统/
作者
Kon4tsu
发布于
2025年5月16日
许可协议