【JVM原理详解】06-类加载器与双亲委派模型
类加载器与双亲委派模型上一篇我们梳理了类加载的完整生命周期其中的加载阶段有一个核心动作——通过类的全限定名获取定义此类的二进制字节流。这个动作由谁来完成答案就是类加载器ClassLoader。类加载器是JVM类加载机制的核心组件它不仅负责查找和加载字节流还决定了类的可见性和隔离性。本篇将深入讲解JVM中各类加载器的层次结构、双亲委派模型的设计原理以及如何自定义类加载器。类加载器的种类JVM默认提供了几种类加载器它们各自负责不同范围的类加载工作形成了一个层次结构。从JDK 8到JDK 11/17加载器的命名和职责有一些变化但核心架构一致。Bootstrap ClassLoader引导类加载器Bootstrap ClassLoader由C实现在HotSpot JVM中是JVM的一部分不是Java类。它负责加载JVM运行所需的核心类库即JAVA_HOME/lib目录下或者被-Xbootclasspath参数指定的路径中的类库。// 获取String类的类加载器System.out.println(String.class.getClassLoader());// 输出: null// null表示Bootstrap ClassLoader因为它不是Java对象注意由于Bootstrap ClassLoader由C实现Java层面无法直接获取到它的引用getClassLoader()返回null就代表该类由Bootstrap ClassLoader加载。Extension ClassLoader / Platform ClassLoader扩展类加载器在JDK 8中称为Extension ClassLoader扩展类加载器由sun.misc.Launcher$ExtClassLoader实现负责加载JAVA_HOME/lib/ext目录下或由java.ext.dirs系统变量指定路径中的类库。从JDK 9开始由于模块化系统Jigsaw的引入扩展类加载器被改名为Platform ClassLoader平台类加载器实现类变为jdk.internal.loader.ClassLoaders$PlatformClassLoader。它负责加载平台模块中的类。// JDK 8// sun.misc.Launcher$ExtClassLoaderxxxSystem.out.println(sun.misc.Launcher.getLauncher().getClassLoader().getParent());// JDK 11// jdk.internal.loader.ClassLoaders$PlatformClassLoaderxxxClassLoaderplatformLoaderClassLoader.getPlatformClassLoader();System.out.println(platformLoader);Application ClassLoader应用程序类加载器Application ClassLoader也称为System ClassLoader系统类加载器由sun.misc.Launcher$AppClassLoaderJDK 8或jdk.internal.loader.ClassLoaders$AppClassLoaderJDK 9实现。它负责加载用户类路径-classpath或-cp上的类库也就是我们自己编写的应用类和第三方依赖。// 获取系统类加载器即Application ClassLoaderClassLoaderappLoaderClassLoader.getSystemClassLoader();System.out.println(appLoader);// 输出类似: jdk.internal.loader.ClassLoaders$AppClassLoaderxxx自定义类加载器除了以上三种类加载器开发者还可以通过继承java.lang.ClassLoader类来实现自定义类加载器。自定义类加载器常见的应用场景包括从网络、加密文件、数据库等非标准来源加载类实现类的隔离如Web容器中不同应用加载同名类的不同版本实现热部署和热替换类加载器层次结构┌─────────────────────────┐ │ Bootstrap ClassLoader │ (C实现, 加载核心类库) │ java.lang.String 等 │ └────────────┬────────────┘ │ 父加载器 ┌────────────▼────────────┐ │ Extension/Platform CL │ (Java实现, 加载扩展模块) │ javax.*, javafx.* 等 │ └────────────┬────────────┘ │ 父加载器 ┌────────────▼────────────┐ │ Application ClassLoader │ (加载classpath) │ 用户类 第三方依赖 │ └────────────┬────────────┘ │ 父加载器 ┌────────────▼────────────┐ │ 自定义 ClassLoader │ (按需加载) │ 网络/加密/动态生成等 │ └─────────────────────────┘需要特别指出这里的父子关系不是继承关系而是组合关系通过parent字段引用。子加载器持有一个指向父加载器的引用形成委托链。双亲委派模型工作流程双亲委派模型Parent Delegation Model的工作流程非常简洁当一个类加载器收到了类加载请求时它首先不会自己去尝试加载这个类而是把这个请求委派给父类加载器去完成。每一个层次的类加载器都是如此因此所有的加载请求最终都应该传送到顶层的Bootstrap ClassLoader中。只有当父加载器反馈自己无法完成这个加载请求它的搜索范围中没有所需的类时子加载器才会尝试自己去加载。用伪代码描述这个逻辑protectedClass?loadClass(Stringname,booleanresolve){// 1. 检查类是否已被加载Class?cfindLoadedClass(name);if(cnull){try{// 2. 委托给父加载器加载if(parent!null){cparent.loadClass(name,false);}else{// 3. 父加载器为null说明到顶了使用Bootstrap加载器cfindBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){// 父加载器无法加载}if(cnull){// 4. 父加载器无法加载自己尝试加载cfindClass(name);}}returnc;}这段逻辑实际就是java.lang.ClassLoader的loadClass方法的核心实现。一个完整的加载流程示例假设我们编写了一个com.example.HelloWorld类放在classpath下。当JVM第一次需要使用这个类时加载流程如下1. Application ClassLoader 收到加载 com.example.HelloWorld 的请求 2. Application ClassLoader 委托给 Extension ClassLoader 3. Extension ClassLoader 委托给 Bootstrap ClassLoader 4. Bootstrap ClassLoader 在 JAVA_HOME/lib 中查找 → 未找到 → 返回null 5. Extension ClassLoader 在 JAVA_HOME/lib/ext 中查找 → 未找到 → 返回null 6. Application ClassLoader 在 classpath 中查找 → 找到HelloWorld.class → 加载成功设计意图双亲委派模型的设计有两个核心目的1. 安全性假设没有双亲委派模型用户可以自定义一个名为java.lang.String的类由Application ClassLoader先加载那么核心API就被篡改了。有了双亲委派模型java.lang.String的加载请求会一路委派到Bootstrap ClassLoader由它从核心类库中加载真正的String类用户的假String永远不会被加载。2. 避免重复加载同一个类被不同类加载器加载会产生不同的Class对象。双亲委派模型保证了Java核心类库只会被Bootstrap ClassLoader加载一次避免了重复加载和类型不一致的问题。Class对象的唯一性这里有一个非常重要的概念对于任何一个类都需要由加载它的类加载器和这个类本身一同确立其在JVM中的唯一性。换句话说比较两个类是否相等只有在这两个类是由同一个类加载器加载的前提下才有意义。publicclassClassIdentityDemo{publicstaticvoidmain(String[]args)throwsException{// 自定义两个不同的类加载器加载同一个class文件ClassLoaderloader1newMyClassLoader(/path/to/classes/);ClassLoaderloader2newMyClassLoader(/path/to/classes/);Class?class1loader1.loadClass(com.example.HelloWorld);Class?class2loader2.loadClass(com.example.HelloWorld);System.out.println(class1class2);// 输出: false// 虽然是同一个class文件但由不同类加载器加载产生不同的Class对象Objectobj1class1.newInstance();Objectobj2class2.newInstance();System.out.println(obj1instanceofcom.example.HelloWorld);// 输出: false —— 这就是类型隔离的本质// obj1的真实类型是 loader1加载的HelloWorld// instanceof检查的是 Application ClassLoader加载的HelloWorld// 两者不是同一个类}}这个特性是类加载器隔离的基础。Tomcat利用它实现Web应用之间的类隔离OSGi利用它实现模块化——这些内容我们将在后续文章中展开。自定义类加载器自定义类加载器通常只需要继承ClassLoader并重写findClass方法。如果不想破坏双亲委派模型不要重写loadClass只重写findClass。下面是一个完整的自定义类加载器示例它从指定磁盘路径加载class文件// 适用: JDK 8/11/17publicclassDiskClassLoaderextendsClassLoader{privateStringclassPath;// class文件所在目录publicDiskClassLoader(StringclassPath){// 不指定parent默认parent为当前线程的上下文类加载器// 通常就是Application ClassLoaderthis.classPathclassPath;}publicDiskClassLoader(StringclassPath,ClassLoaderparent){super(parent);this.classPathclassPath;}OverrideprotectedClass?findClass(Stringname)throwsClassNotFoundException{byte[]classDataloadClassData(name);if(classDatanull){thrownewClassNotFoundException(无法找到类: name);}// defineClass将字节数组转换为Class对象// 这是JVM提供的原生方法负责验证、解析等底层工作returndefineClass(name,classData,0,classData.length);}privatebyte[]loadClassData(Stringname){// 将类名转换为文件路径: com.example.HelloWorld → com/example/HelloWorld.classStringpathclassPath/name.replace(.,/).class;try{PathfilePaths.get(path);if(!Files.exists(file)){returnnull;// 返回null表示自己无法加载}returnFiles.readAllBytes(file);}catch(IOExceptione){returnnull;}}}使用自定义类加载器publicclassDiskClassLoaderDemo{publicstaticvoidmain(String[]args)throwsException{DiskClassLoaderloadernewDiskClassLoader(D:/custom-classes);// 加载com.example.HelloWorldClass?clazzloader.loadClass(com.example.HelloWorld);// 通过反射创建实例并调用方法Objectinstanceclazz.getDeclaredConstructor().newInstance();Methodmethodclazz.getMethod(sayHello);method.invoke(instance);// 查看加载器System.out.println(clazz.getClassLoader());// 输出: DiskClassLoaderxxx如果是自定义加载器加载的话// 如果D:/custom-classes/com/example/HelloWorld.class不存在// 由于双亲委派最终会由Application ClassLoader从classpath中加载// 输出将是: jdk.internal.loader.ClassLoaders$AppClassLoaderxxx}}自定义类加载器何时打破双亲委派如果自定义类加载器只重写了findClass它会遵循双亲委派模型——先委托父加载器加载父加载器加载不了才自己加载。但在某些场景下我们需要打破双亲委派比如隔离需求Tomcat的WebApp类加载器优先自己加载而不是先委托父加载器热部署需要重新加载已修改的类而父加载器中缓存的旧版本无法更新SPI机制父加载器需要访问子加载器的类下一篇详细讲解打破双亲委派的方法是重写loadClass方法publicclassCustomClassLoaderextendsClassLoader{privateStringclassPath;publicCustomClassLoader(StringclassPath,ClassLoaderparent){super(parent);this.classPathclassPath;}OverrideprotectedClass?loadClass(Stringname,booleanresolve)throwsClassNotFoundException{// 1. 检查是否已加载Class?cfindLoadedClass(name);if(cnull){// 2. 对自己负责的类优先自己加载打破双亲委派if(name.startsWith(com.myapp.)){try{cfindClass(name);}catch(ClassNotFoundExceptione){// 自己加载失败再走父加载器}}// 3. 其他类走正常的双亲委派if(cnull){if(getParent()!null){cgetParent().loadClass(name);}else{thrownewClassNotFoundException(name);}}}if(resolve){resolveClass(c);}returnc;}OverrideprotectedClass?findClass(Stringname)throwsClassNotFoundException{byte[]dataloadClassData(name);if(datanull)thrownewClassNotFoundException(name);returndefineClass(name,data,0,data.length);}privatebyte[]loadClassData(Stringname){StringpathclassPath/name.replace(.,/).class;try{returnFiles.readAllBytes(Paths.get(path));}catch(IOExceptione){returnnull;}}}实践要点优先使用findClass而非loadClass除非你有明确的隔离需求否则始终重写findClass保持双亲委派。随意打破双亲委派可能导致核心API被篡改、类加载混乱等严重问题。defineClass的不可逆性一旦defineClass成功这个类的Class对象就会被锁定在该类加载器中。即使后续字节码文件被修改同一个类加载器再次defineClass同名类会抛出LinkageError。热部署必须创建新的类加载器实例。避免类加载器内存泄漏类加载器引用了大量Class对象如果类加载器无法被GC回收如线程的ContextClassLoader引用会导致Metaspace持续增长。在使用线程池时尤其需要注意。JDK 9模块化对类加载器的影响JDK 9引入模块化后类加载器之间增加了模块层的概念。java.lang.ClassLoader增加了getNamedModule()方法BootClassLoader的加载范围通过模块定义而非路径。但双亲委派模型的基本架构没有改变。-Xbootclasspath/a的使用如果需要将自定义的类加入引导类加载器的加载范围如覆盖或补充核心类可以使用-Xbootclasspath/a:/path/to/your.jar/a表示append追加到末尾。java-Xbootclasspath/a:./supplement.jar-jaryour-app.jar小结JVM类加载器分为Bootstrap ClassLoaderC实现加载核心库、Extension/Platform ClassLoader加载扩展模块、Application ClassLoader加载classpath以及自定义类加载器。双亲委派模型要求类加载请求先委派给父加载器父加载器无法加载时子加载器才自行加载。其设计目的是保证安全性和避免重复加载。类的唯一性由类加载器类名共同确定。同一个class文件被不同类加载器加载后产生不同的Class对象instanceof检查会返回false。自定义类加载器继承ClassLoader并重写findClass可保持双亲委派重写loadClass则可打破双亲委派。下一篇我们将探讨双亲委派模型的局限性深入SPI机制和线程上下文类加载器如何打破这一模型。

相关新闻

SolidWorks_焊件设计9_子焊件管理

SolidWorks_焊件设计9_子焊件管理

子焊件管理 摘要 在大型机械结构、钢结构框架或复杂焊接件的设计过程中,将整体焊件合理拆分为子焊件是一种至关重要的工程实践。本文深入探讨了子焊件管理的核心理念、技术实现方法及其在工程出图中的实际应用。通过详细的理论分析和完整的代码示例,展示…

2026/10/19 21:55:29 阅读更多 →
SolidWorks_焊件设计8_多草图骨架布局

SolidWorks_焊件设计8_多草图骨架布局

多草图骨架布局:利用多个2D/3D草图构建复杂空间框架结构 摘要 在计算机图形学、CAD辅助设计、游戏开发以及建筑信息模型(BIM)等领域,构建复杂的空间框架结构一直是一个核心挑战。传统的多边形建模或参数化建模往往需要大量的手动调…

2026/10/18 9:47:57 阅读更多 →
【独家】基于217个真实AI项目复盘的场景适配决策树(含GPU成本/延迟/准确率三维度阈值标定)

【独家】基于217个真实AI项目复盘的场景适配决策树(含GPU成本/延迟/准确率三维度阈值标定)

更多请点击: https://codechina.net 第一章:AI模型适用场景分析 AI模型并非万能工具,其价值高度依赖于具体业务需求与数据特性。选择合适模型的关键在于理解任务类型、数据规模、实时性要求及可解释性约束。脱离场景空谈“大模型”或“小模型…

2026/10/19 10:08:04 阅读更多 →

最新新闻

对齐的幻觉:AI模型对齐中的认知陷阱与实操避坑指南

对齐的幻觉:AI模型对齐中的认知陷阱与实操避坑指南

1. 从“对齐的幻觉”说起:一个被过度简化的复杂命题“对齐”这个词,在过去两年里被提及的频率高得离谱。不管是大模型训练、产品设计,还是团队协作,似乎只要挂上“对齐”两个字,问题就解决了一半。但实际情况是&#x…

2026/10/20 6:50:43 阅读更多 →
农业害虫检测数据集构建与CNN识别模型实战解析

农业害虫检测数据集构建与CNN识别模型实战解析

做农业害虫识别,最让人头疼的往往不是算法选型,而是数据本身。我在这个方向上折腾了快两年,踩过的坑比写过的代码还多。这套关于农业害虫检测数据集与CNN识别模型的项目,本质上解决的核心问题就一个:怎么让模型在田间地…

2026/10/20 6:50:43 阅读更多 →
pascal-5i数据集小样本分割实战:fold划分、episodic采样与避坑指南

pascal-5i数据集小样本分割实战:fold划分、episodic采样与避坑指南

简介:Pascal-5i 数据集面向小样本学习方向的研究者与算法工程师,基于经典 PASCAL VOC 的 20 个类别,通过采样策略划分基础类别与新类别,支持 5-way 1-shot 与 5-way 5-shot 场景,用于评估模型在极少样本下的泛化与适应…

2026/10/20 6:50:43 阅读更多 →
AI建站与传统建站深度对比:流程、架构与长期成本全解析

AI建站与传统建站深度对比:流程、架构与长期成本全解析

1. 从两个真实场景说起:为什么这个话题值得聊去年帮一个做手工皮具的朋友看他的独立站,他花了小两万找外包公司做的,上线三个月,产品页的跳出率一直在70%以上。我打开页面一看,首屏是一张巨大的轮播图,加载…

2026/10/20 6:50:43 阅读更多 →
AI超节点硬件设计:HBD电互联与高速链路工程实践

AI超节点硬件设计:HBD电互联与高速链路工程实践

1. 从一颗芯片到一台超节点:为什么硬件形态成了大模型时代的胜负手聊到大模型训练,大多数人第一反应是算法、是参数量、是数据质量。但真正在机房里摸爬滚打过的人都知道,当集群规模跨过千卡门槛之后,决定训练效率的往往不再是模型…

2026/10/20 6:50:43 阅读更多 →
WorkBuddy AI漫剧:文字一键转专业漫剧的结构化叙事引擎

WorkBuddy AI漫剧:文字一键转专业漫剧的结构化叙事引擎

1. 项目概述:WorkBuddy AI漫剧到底是什么,它凭什么让B站用户集体“真香”WorkBuddy AI漫剧——这名字刚在B站弹幕里刷出来时,我第一反应是又一个套壳PPT动画工具。直到连续三天蹲守某UP主的更新节奏,亲眼看着他用同一段30秒口播文…

2026/10/20 6:49:43 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →