资讯动态

揭秘类加载机制:双亲委派模型的真相与打破!

发布时间:2026/10/14 6:28:56 来源:尧图企业网站定制
全文目录开篇语0. 前言那年我瞎改了一个类结果整个项目都崩了 1. 什么是“类加载”请客入座的流程2. 类加载器ClassLoader请客的“管家” 3. 双亲委派模型Parent-Delegation Model严格的“家规” ‍‍‍ 为什么要这么设计好处多多4. 打破双亲委派模型偶尔也得“逆子”一把 ‍ 代码实战看类加载器本尊5. 结语深入 JVM做更酷的全栈文末开篇语哈喽各位小伙伴们你们好呀我是喵手。运营社区C站/掘金/腾讯云/阿里云/华为云/51CTO欢迎大家常来逛逛今天我要给大家分享一些自己日常学习到的一些知识点并以文字的形式跟大家一起交流互相学习一个人虽可以走的更快但一群人可以走的更远。我是一名后端开发爱好者工作日常接触到最多的就是Java语言啦所以我都尽量抽业余时间把自己所学到所会的通过文章的形式进行输出希望以这种方式帮助到更多的初学者或者想入门的小伙伴们同时也能对自己的技术进行沉淀加以复盘查缺补漏。小伙伴们在批阅的过程中如果觉得文章不错欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持0. 前言那年我瞎改了一个类结果整个项目都崩了 还记得我刚工作那会儿有个需求要修改一个第三方 Jar 包里的类。我仗着自己“艺高人胆大”直接把 Jar 包解压找到对应的.class文件改了一通然后打成新的 Jar 包替换上去。结果呢部署到服务器上项目启动失败各种ClassNotFoundException或者NoSuchMethodError直接把我整懵了当时的我百思不得其解我明明把文件放进去了怎么就找不到呢后来才知道JVM 加载类可不是你把文件扔进去就行的它有一整套严谨的“身份证查验”和“背景调查”流程。而这个流程的核心就是今天我们要聊的——类加载机制和双亲委派模型学好这个不仅能让你在面试中脱颖而出更能让你在排查各种奇葩的类冲突、版本不兼容问题时如同拥有“透视眼”一般一眼看穿本质✨1. 什么是“类加载”请客入座的流程咱们写的 Java 代码最终会被编译成.class文件。这些.class文件JVM 并不能直接拿来用。它得先把这些文件加载到内存里并且解析、初始化然后才能变成可用的 Java 对象。这个从.class文件到内存中的Class对象的过程就叫类加载。类加载的过程大致可以分为 7 个阶段1.加载Loading* 找到.class文件。* 将.class文件读取成二进制字节流。* 将这个字节流的静态存储结构转化为方法区的运行时数据结构。* 在内存中生成一个代表这个类的java.lang.Class对象。2.验证Verification确保.class文件格式正确、语义合法、符合 JVM 规范别有啥恶意代码或者不安全的指令。这是 JVM 的“安检”3.准备Preparation为类的静态变量分配内存并初始化为零值比如int是 0boolean是false引用类型是null。注意这时候还没执行任何 Java 代码哦4.解析Resolution将常量池里的符号引用比如java.lang.Object的字符串替换为直接引用直接指向内存中的地址。5.初始化Initialization*这才是真正执行类构造器clinit()方法的时候* 在这个阶段会执行静态代码块、静态变量的赋值操作。* 初始化只发生一次且是线程安全的。后面还有使用Using和卸载Unloading但最核心的是前面这五步。2. 类加载器ClassLoader请客的“管家” 谁来负责执行上面“加载”这第一步呢就是类加载器ClassLoader。JVM 默认有 3 种类加载器它们就像不同级别的管家负责从不同地方“请”类进来启动类加载器Bootstrap ClassLoader老大用 C 实现所以它不是java.lang.ClassLoader的子类。负责加载JAVA_HOME/lib目录下的核心类库rt.jar等以及-Xbootclasspath参数指定路径的类。你可以把它理解成“系统管理员”负责加载 JVM 运行的基石连 Java 自己都管不了它。扩展类加载器Extension ClassLoaderExtClassLoader。负责加载JAVA_HOME/lib/ext目录下的所有 Jar 包以及java.ext.dirs系统变量指定的路径中的类库。它有点像“高级助手”负责加载一些标准扩展类。应用程序类加载器Application ClassLoaderAppClassLoader也叫系统类加载器System ClassLoader。负责加载用户类路径classpath上指定的类库。我们自己写的代码就是它来加载的它是所有用户自定义类加载器的默认父加载器。这三个类加载器之间可不是平级关系它们之间有一个非常重要的“父子”层级结构这个结构就是双亲委派模型的核心3. 双亲委派模型Parent-Delegation Model严格的“家规” ‍‍‍好了高潮来了这就是面试官最爱问的也是最能体现你对 JVM 理解深度的地方。什么是双亲委派模型当一个类加载器收到类加载请求时它并不会马上去尝试加载这个类而是首先把这个请求委派给它的父类加载器去完成。只有当父类加载器无法完成加载在它的搜索路径下找不到该类时子类加载器才会尝试自己去加载。整个流程AppClassLoader收到加载请求 - 委派给ExtClassLoader-ExtClassLoader再委派给Bootstrap ClassLoader。如果Bootstrap ClassLoader找不到就返回给ExtClassLoader自己找。如果ExtClassLoader也找不到就返回给AppClassLoader自己找。如果AppClassLoader也找不到那才抛出ClassNotFoundException生动比喻你AppClassLoader想要找一本书某个类。你先问你爸ExtClassLoader“爸这本书你有吗”你爸不直接回答他先问他爸Bootstrap ClassLoader“爸您有这本书吗”你爷爷说“我没有核心库里没这书。”你爸这才自己去他书架上找扩展库里找找到了就给你。如果他也找不到他才会跟你说“儿子你再去你自己的书架上找找吧。”你才自己去你的书架上找。如果连你也找不到那你就真的没这本书了。 为什么要这么设计好处多多避免重复加载爹妈都加载过的类儿子就没必要再加载了。保证一个类在 JVM 中只有一份Class对象。保证核心 API 的安全这是最重要的如果允许用户自己写一个java.lang.String类并加载那后果不堪设想有了双亲委派你想加载java.lang.StringAppClassLoader会委派给ExtClassLoader再委派给Bootstrap ClassLoader。Bootstrap ClassLoader发现自己已经加载过JDK里的String类了就直接返回这个核心类。你的“山寨”String根本没机会被加载这就是 JVM 的“沙箱安全机制”4. 打破双亲委派模型偶尔也得“逆子”一把 虽然双亲委派模型好处多多但有时候它也会成为我们的“桎梏”。比如JDBC 驱动加载、Tomcat/Spring 等框架都需要打破双亲委派。为啥要打破以JDBC为例JDBC 核心接口java.sql.Driver等是由Bootstrap ClassLoader加载的因为是 JDK 核心库。但是具体的 JDBC 驱动实现比如mysql-connector-java.jar是我们引入的第三方 Jar 包它是由AppClassLoader加载的。问题来了Bootstrap ClassLoader无法“看到”AppClassLoader加载的类爹看不到儿子的东西啊所以如果按照双亲委派Bootstrap ClassLoader在加载Driver接口时需要实例化具体的驱动但它找不到具体的实现类。怎么办Java 设计者引入了线程上下文类加载器Thread Context ClassLoader, TCCL。TCCL允许父类加载器“请求”子类加载器去完成类加载的动作。这就像儿子问爹要钱正常委派结果爹没钱反过来让儿子给钱打破委派具体到 JDBC就是Bootstrap ClassLoader发现需要加载驱动实现时它会获取当前线程的TCCL(通常是AppClassLoader)然后让TCCL去加载驱动类。‍ 代码实战看类加载器本尊publicclassClassLoaderHierarchy{publicstaticvoidmain(String[]args){// 1. 获取应用程序类加载器ClassLoaderappClassLoaderClassLoaderHierarchy.class.getClassLoader();System.out.println(AppClassLoader: appClassLoader);// 2. 获取扩展类加载器 (AppClassLoader 的父类加载器)ClassLoaderextClassLoaderappClassLoader.getParent();System.out.println(ExtClassLoader: extClassLoader);// 3. 启动类加载器 (ExtClassLoader 的父类加载器)// 注意它不是java对象所以会返回nullClassLoaderbootstrapClassLoaderextClassLoader.getParent();System.out.println(BootstrapClassLoader: bootstrapClassLoader);// 输出 null因为是 C 实现System.out.println(----------------------------------------);// 看看咱们自己写的类是谁加载的System.out.println(当前类加载器: ClassLoaderHierarchy.class.getClassLoader());// 看看 Object (核心类) 是谁加载的System.out.println(java.lang.Object 的类加载器: Object.class.getClassLoader());// 输出 null说明是 Bootstrap 加载的// 看看一个扩展包里的类是谁加载的(比如 sun.misc.Launcher$ExtClassLoader)// 你可以尝试加载一些JDK自带的扩展类例如 com.sun.tools.attach.AttachProvidertry{Class?extClassClass.forName(sun.nio.cs.StreamEncoder);// 这个类通常在 rt.jar 但实际是由 ExtClassLoader 加载System.out.println(sun.nio.cs.StreamEncoder 的类加载器: extClass.getClassLoader());}catch(ClassNotFoundExceptione){System.err.println(找不到类 sun.nio.cs.StreamEncoder);}// 演示 TCCLSystem.out.println(----------------------------------------);System.out.println(当前线程的上下文类加载器 (TCCL): Thread.currentThread().getContextClassLoader());}}5. 结语深入 JVM做更酷的全栈JVM 的类加载机制和双亲委派模型是不是比你想象的要复杂和有趣得多它不仅仅是一个技术细节更体现了 Java 平台在安全、稳定、高效方面深思熟虑的设计哲学。理解了这些下次当你遇到线上 Jar 包冲突导致的NoSuchMethodError。Tomcat 部署多个 Web 应用时每个应用都有独立的类加载器体系。或者想实现插件化热插拔功能需要自定义类加载器时。你就能从容不迫一眼看穿问题的本质而不是只会抓耳挠腮、盲目尝试了这就是从“被动解决问题”到“主动掌控问题”的巨大飞跃全栈开发嘛不仅要能把功能实现更要能把系统“玩”得转咱们继续加油把这些底层的东西都吃透你就是下一个技术大佬 感觉这波知识点吸收得还不错吧给我个小鼓励呗… …文末好啦以上就是我这期的全部内容如果有任何疑问欢迎下方留言哦咱们下期见。… …学习不分先后知识不分多少事无巨细当以虚心求教三人行必有我师焉wished for you successed ⭐️若喜欢我就请关注我叭。⭐️若对您有用就请点赞叭。⭐️若有疑问就请评论留言告诉我叭。版权声明本文由作者原创转载请注明出处谢谢支持

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑