
【JVM】详细介绍类加载器【一】类加载器核心作用【二】JVM 内置三大核心类加载器启动顺序自上而下【1】启动类加载器 Bootstrap ClassLoader【2】扩展类加载器 Extension ClassLoader【3】应用程序类加载器 Application ClassLoader系统类加载器【三】自定义类加载器用户自己实现 ClassLoader / URLClassLoader1两种实现方式2使用场景3关键特性【四】双亲委派模型双亲委托机制【1】工作流程加载一个类时标准流程【2】双亲委派两大核心优势【3】打破双亲委派模型什么时候需要破坏1典型场景2如何打破双亲委派【五】类加载完整 5 大阶段加载、验证、准备、解析、初始化【1】加载ClassLoader 专属阶段【2】验证【3】准备【4】解析【5】初始化clinit【六】类的卸载机制和 ClassLoader 强绑定元空间回收核心【1】类能被卸载的三个必要条件缺一不可【2】哪些类永远无法卸载【3】元空间泄漏根源【七】线程上下文类加载器 TCCL解决双亲委派缺陷【1】背景问题【2】TCCL 作用【3】典型使用【八】Tomcat 自定义类加载器模型打破双亲典型案例【九】易混淆关键总结【一】类加载器核心作用类加载器ClassLoader是 JVM「类加载子系统」的核心组件职责根据全限定类名读取.class字节码文件转换成内存中java.lang.Class对象隔离类命名空间不同类加载器加载的同名类是完全独立的两个 Class相互不可转换实现类的按需延迟加载懒加载不会一次性加载所有类配合双亲委派模型保障核心类安全防止自定义污染 JDK 基础类控制类卸载只有自定义 ClassLoader 被 GC它加载的类才有可能被卸载元空间回收。核心底层规则每个Class对象内部持有ClassLoader引用两个类相等的判断条件类全限定名 同一个类加载器实例全部相同才视为同一个类。【二】JVM 内置三大核心类加载器启动顺序自上而下遵循层级父子关系Bootstrap - Extension - Application【1】启动类加载器 Bootstrap ClassLoader实现C 编写不属于 Java 体系无ClassLoader实例String.class.getClassLoader()返回null加载路径jre/lib下核心 rt.jar、charsets.jar、security 等 JDK 基础包加载内容java.lang、java.io、java.util、java.math 等 JDK 底层核心类特点顶层父加载器无上级不继承ClassLoader无法通过 Java 代码直接获取与扩展保护核心基础类防止用户篡改。【2】扩展类加载器 Extension ClassLoader实现Java 实现sun.misc.Launcher$ExtClassLoader父加载器Bootstrap底层 C 加载器加载路径jre/lib/ext扩展目录下所有 jar用途存放系统扩展工具包自定义第三方扩展库放此目录会被自动加载应用场景加密工具、日志扩展、安全扩展等系统级插件。【3】应用程序类加载器 Application ClassLoader系统类加载器实现sun.misc.Launcher$AppClassLoader父加载器ExtClassLoader加载路径classpath路径项目编译后的 class、依赖 jar 包maven 引入的第三方包作用我们日常业务代码、Spring、MyBatis 等框架全部由它加载获取方式ClassLoader.getSystemClassLoader()。【三】自定义类加载器用户自己实现 ClassLoader / URLClassLoader1两种实现方式继承ClassLoader重写findClass(String name)推荐标准方式继承URLClassLoader可加载本地磁盘、网络 URL 上的 jar/class如热部署、代码加密2使用场景热部署 / 热更新Spring DevTools、JRebel每次更新创建新 ClassLoader旧加载器连同旧类一起卸载加密 class 文件编译后加密自定义加载器读取后解密再定义类动态加载远程 jar插件化架构IDE 插件、中间件动态插件隔离多版本依赖同一服务共存多个版本框架避免包冲突。3关键特性自定义加载器会作为 AppClassLoader 的子加载器参与双亲委派模型只有自定义 ClassLoader 能被 GC才会产生类卸载能力原生三大加载器生命周期和 JVM 一致加载的类永远无法卸载。【四】双亲委派模型双亲委托机制【1】工作流程加载一个类时标准流程当 ClassLoader 收到加载类请求loadClass(name)检查当前加载器是否已经加载过该类有则直接返回缓存 Class没有则将加载请求委托给父加载器执行父加载器重复上述逻辑层层向上委托直到 Bootstrap ClassLoaderBootstrap 尝试在核心 jar 查找该类找到则返回 Class找不到向下回退回退到子加载器调用自身findClass()本地加载字节码生成 Class 对象缓存该 Class后续直接复用。流程图自定义 ClassLoader → AppClassLoader → ExtClassLoader → BootstrapClassLoader向上委托查找顶层找不到再逐层向下自己加载。【2】双亲委派两大核心优势安全隔离防止核心类篡改比如用户自定义java.lang.String加载请求会委托到 Bootstrap它已经加载原生 String直接返回自定义的恶意类永远不会被加载避免破坏 JDK 基础类。类全局唯一避免重复加载同一个类只会由顶层能找到它的加载器加载一次节省内存防止类型转换异常。【3】打破双亲委派模型什么时候需要破坏标准双亲是向上委托破坏即不优先委托父加载器自己先加载。1典型场景SPI 服务提供者接口JDBC 驱动、Slf4j、Dubbo SPIJDK 核心包定义接口Driver 接口由 Bootstrap 加载驱动实现类在第三方 jarApp 加载器父加载器看不到子类资源因此使用线程上下文类加载器 Thread.getContextClassLoader ()反向委派。热部署、插件化框架需要自己优先加载新 class不交给父加载器实现类隔离、动态卸载。OSGi、Tomcat 容器Tomcat 每个 Web 应用使用独立自定义类加载器优先加载应用内部 class实现多 web 包依赖隔离天然打破双亲委派。2如何打破双亲委派重写loadClass()方法修改委托逻辑不再优先向上找父加载器注意只重写findClass()不会破坏双亲仅自定义本地加载逻辑。【五】类加载完整 5 大阶段加载、验证、准备、解析、初始化类加载器负责前 3 阶段解析 初始化配合 JVM 完成【1】加载ClassLoader 专属阶段类加载器读取全限定名获取二进制字节流将字节流转换为方法区元空间的运行时数据结构在堆中生成该类对应的java.lang.Class对象作为元数据访问入口。【2】验证校验字节码合法性防止恶意 / 畸形字节码破坏 JVM文件格式、元数据、字节码指令、符号引用校验。【3】准备为类静态变量分配内存堆内 Class 对象赋予默认零值不执行业务赋值例static int a 10;此时 a0。【4】解析把元空间运行时常量池中的符号引用转为直接内存引用类、方法、字段指针。【5】初始化执行静态代码块、静态变量赋值逻辑给静态变量赋予程序员定义的值主动引用才会触发初始化new 对象、调用静态方法 / 静态变量、反射、main 入口、子类初始化。【六】类的卸载机制和 ClassLoader 强绑定元空间回收核心【1】类能被卸载的三个必要条件缺一不可该类所有实例对象全部被 GC 回收堆中无任何实例加载该类的自定义 ClassLoader 实例被 GC Roots 不可达该类对应的Class对象不存在任何强引用无反射缓存、无静态集合持有 Class。【2】哪些类永远无法卸载Bootstrap、Ext、App 三大内置加载器加载的所有类因为这三个加载器是 JVM 全局强引用永远不会被回收对应的类常驻元空间。【3】元空间泄漏根源频繁创建自定义 ClassLoaderSpring 热部署、CGLIB 动态类、脚本引擎旧加载器被强引用无法回收 → 加载的类无法卸载 → 元空间持续上涨频繁 Full GC最终 Metaspace OOM。【七】线程上下文类加载器 TCCL解决双亲委派缺陷【1】背景问题双亲委派只能向上委托父加载器无法访问子加载器的类SPI 场景JDK 内置接口父加载器需要调用第三方实现类子加载器原生双亲做不到。【2】TCCL 作用每个线程持有独立上下文类加载器默认继承父线程的 AppClassLoader允许代码反向委派父加载器通过线程上下文加载子加载器的类。【3】典型使用JDBCDriverManagerBootstrap 加载通过Thread.currentThread().getContextClassLoader()加载 mysql 驱动实现类Spring、Dubbo、日志框架大量使用 TCCL 做资源加载。【八】Tomcat 自定义类加载器模型打破双亲典型案例Tomcat 为每个 WebApp 创建独立WebappClassLoader加载优先级优先加载当前 web 应用目录下 class/jarWEB-INF再委托父加载器Common、System、Ext、Bootstrap目的多个 war 包依赖不同版本 jar 互不冲突停止 web 应用时销毁 WebappClassLoader卸载该应用所有类释放元空间。层级Bootstrap - Ext - CommonClassLoader - WebappClassLoader(每个应用独立)【九】易混淆关键总结加载器分层Bootstrap (C) → Ext → App → 自定义 ClassLoader双亲委派加载类先向上委托顶层找不到再自身加载重写 loadClass 可打破只有自定义 ClassLoader 加载的类才有卸载可能原生三大加载器类永久驻留元空间类唯一性 全类名 同一个 ClassLoader 实例不同加载器同名类强转会报 ClassCastExceptionTCCL 线程上下文类加载器用于反向加载解决 SPI 双亲委派局限性热部署、插件化、加密 class 都基于自定义 ClassLoader 实现自定义 ClassLoader 长期不释放是元空间内存泄漏最常见诱因。