【JVM】类加载全过程双亲委派机制深度解析 大家好我是程序员二叉。简介本文梳理后端面试必考的类加载五大步骤、双亲委派机制原理、机制优缺点、打破方案、自定义类加载器完整实现逻辑附带可运行代码示例。欢迎点赞关注收藏。一、JVM类加载的五个步骤类从磁盘.class文件到内存实例化对象完整分为加载、验证、准备、解析、初始化五个阶段。1. 加载 Loading根据类的全限定名读取二进制字节流将字节流转换成方法区中的运行时数据结构在堆中生成代表这个类的Class对象作为访问方法区类信息的入口。2. 验证 Verification校验字节码合法性、安全性防止恶意代码破坏虚拟机分为四层校验文件格式验证校验字节流是否符合Class文件规范元数据验证校验类的语义、继承关系等基础语法字节码验证校验方法体内指令逻辑安全符号引用验证校验引用的外部类/字段/方法是否可访问存在。3. 准备 Preparation给类中static静态变量分配内存空间内存位于方法区给静态变量赋默认零值而非代码中赋值示例static int num 100准备阶段num0初始化阶段才赋值100常量static final编译期直接赋值准备阶段就赋予确定值。4. 解析 Resolution把常量池里的符号引用替换成内存中真实的直接引用解析对象包含类/接口、字段、普通方法、接口方法、方法句柄、调用点限定符等。5. 初始化 Initialization类加载最后一步真正执行Java代码逻辑自动生成并执行类构造器clinit()方法按代码顺序给静态变量赋予代码定义的值顺序执行静态代码块只有主动引用类时才会触发初始化被动引用不会执行初始化。二、什么是双亲委派机制1. 三层原生类加载器层级启动类加载器 Bootstrap ClassLoaderC实现加载JAVA_HOME/lib核心rt.jar等基础类扩展类加载器 Extension ClassLoaderJava实现加载JAVA_HOME/lib/ext扩展包应用程序类加载器 App ClassLoader系统默认加载器加载项目classpath下自定义代码、第三方依赖包。2. 委派执行流程当一个类加载器收到加载请求自身先不尝试加载向上委托父加载器处理层层向上传递直到最顶层启动类加载器顶层加载器无法加载时再逐级向下由子加载器尝试加载全部加载失败抛出ClassNotFoundException。3. 双亲委派核心优势安全防护防止恶意篡改Java核心类比如自定义java.lang.String无法覆盖原生类全局唯一性保证同一个全限定名的类在JVM中只存在一份Class实例避免重复加载父加载器加载成功后子类直接复用减少IO加载开销。三、双亲委派机制的缺点上层加载器无法访问下层加载器的类启动类加载器、扩展加载器不能识别应用加载器加载的业务类上下单向隔离SPI服务扩展场景适配困难如JDBC、日志框架SPI核心接口由启动类加载器加载但实现类在项目classpath默认委派模式拿不到实现类模块化、热部署、插件化场景受限OSGi、Tomcat多web应用隔离、代码热更新等场景需要独立隔离类加载环境原生委派无法实现无法实现类隔离多个模块依赖不同版本Jar包时双亲委派只会加载第一个找到的Jar版本冲突无法隔离。四、如何打破双亲委派机制1. 底层原理ClassLoader加载入口是loadClass(String name)原生方法内置双亲委派逻辑重写loadClass()方法删除向上委托逻辑即可打破。2. 三代打破方案第一代重写loadClass()完全重写加载逻辑跳过父加载器委托早期Tomcat、OSGi使用第二代线程上下文类加载器 ContextClassLoaderSPI标准解决方案核心接口由启动加载器加载通过线程上下文切换成应用加载器去加载实现类第三代模块化自定义层级自定义平行类加载器不遵循父层级用于插件、多版本Jar隔离。典型打破场景JDBC驱动加载、Dubbo SPI、Spring SPITomcat多个Web工程独立类隔离OSGi模块化框架、开发工具热部署中间件插件化架构。五、如何实现自定义类加载器规范实现规则继承ClassLoader父类遵循双亲委派只重写findClass()不要改动loadClass()在findClass中读取.class字节码调用defineClass()转换成Class对象若要打破委派重写loadClass()屏蔽parent委托逻辑。示例1标准遵循双亲委派的自定义加载器importjava.io.File;importjava.io.FileInputStream;publicclassCustomClassLoaderextendsClassLoader{// 自定义class文件存放路径privatefinalStringclassDir;publicCustomClassLoader(StringclassDir){this.classDirclassDir;}// 只重写findClass保留原生双亲委派逻辑OverrideprotectedClass?findClass(StringclassName)throwsClassNotFoundException{try{// 1. 读取class文件字节数组byte[]classBytesreadClassFile(className);if(classBytesnull){thrownewClassNotFoundException();}// 2. 字节码转为Class对象returndefineClass(className,classBytes,0,classBytes.length);}catch(Exceptione){thrownewClassNotFoundException(className,e);}}// 读取磁盘.class文件privatebyte[]readClassFile(StringclassName)throwsException{StringpathclassDirFile.separatorclassName.replace(.,File.separator).class;try(FileInputStreamfisnewFileInputStream(path)){returnfis.readAllBytes();}}// 测试调用publicstaticvoidmain(String[]args)throwsException{CustomClassLoaderloadernewCustomClassLoader(D:/classpath);Class?testClsloader.loadClass(com.demo.Test);ObjectobjtestCls.newInstance();System.out.println(obj);}}示例 2打破双亲委派重写 loadClasspublicclassBreakDelegateClassLoaderextendsClassLoader{privatefinalStringclassDir;publicBreakDelegateClassLoader(StringclassDir){this.classDirclassDir;}// 重写加载入口跳过父加载器委托OverridepublicClass?loadClass(Stringname)throwsClassNotFoundException{// 1. 先查询缓存是否已加载Class?cacheClassfindLoadedClass(name);if(cacheClass!null){returncacheClass;}// 不向上委托父加载器直接自己加载try{byte[]bytesreadBytes(name);returndefineClass(name,bytes,0,bytes.length);}catch(Exceptione){// 自己加载失败再交给父类兜底returnsuper.loadClass(name);}}privatebyte[]readBytes(StringclassName)throwsException{// 读取class文件逻辑省略同上示例returnnewbyte[0];}}总结面试速记版类加载五步加载 → 验证 → 准备 → 解析 → 初始化双亲委派向上委托父加载器父失败再子类加载优点安全防篡改、类唯一、无重复加载缺点上下隔离、SPI 不兼容、插件化受限打破核心重写loadClass()主流方案上下文类加载器标准自定义加载器继承ClassLoader重写findClass()打破委派重写loadClass()。