类加载器ClassLoader源码解析
1、ClassLoader作用
- 类加载流程的"加载"阶段是由类加载器完成的。
2、类加载器结构
结构:BootstrapClassLoader(祖父)-->ExtClassLoader(爷爷)-->AppClassLoader(也称为SystemClassLoader)(爸爸)-->自定义类加载器(儿子)
关系:看括号中的排位;彼此相邻的两个为父子关系,前为父,后为子
2.1、BootstrapClassLoader
- 下边简称为boot
- C++编写
- 为ExtClassLoader的父类,但是通过ExtClassLoader的getParent()获取到的是null(在类加载器部分:null就是指boot)
- 主要加载:E:\Java\jdk1.6\jre\lib\*.jar(最重要的就是:rt.jar)
2.2、ExtClassLoader:
- 下边简称为ext
- java编写,位于sun.misc包下,该包在你导入源代码的时候是没有的,需要重新去下
- 主要加载:E:\Java\jdk1.6\jre\lib\ext\*.jar(eg.dnsns.jar)
2.3、AppClassLoader:
- 下边简称为app
- java编写,位于sun.misc包下
- 主要加载:类路径下的jar
2.4、自定义类加载器:
- 下边简称为custom
- 自己编写的类加载器,需要继承ClassLoader类或URLClassLoader,并至少重写其中的findClass(String name)方法,若想打破双亲委托机制,需要重写loadClass方法
- 主要加载:自己指定路径的class文件
3、全盘负责机制
概念:假设ClassLoaderA要加载class B,但是B引用了class C,那么ClassLoaderA先要加载C,再加载B,"全盘"的意思就是,加载B的类加载器A,也会加载B所引用的类
4、双亲委托机制
这也是类加载器加载一个类的整个过程。
过程:假设我现在从类路径下加载一个类A,
1)那么app会先查找是否加载过A,若有,直接返回;
2)若没有,去ext检查是否加载过A,若有,直接返回;
3)若没有,去boot检查是否加载过A,若有,直接返回;
4)若没有,那就boot加载,若在E:\Java\jdk1.6\jre\lib\*.jar下找到了指定名称的类,则加载,结束;
5)若没找到,boot加载失败;
6)ext开始加载,若在E:\Java\jdk1.6\jre\lib\ext\*.jar下找到了指定名称的类,则加载,结束;
7)若没找到,ext加载失败;
8)app加载,若在类路径下找到了指定名称的类,则加载,结束;
9)若没有找到,抛出异常ClassNotFoundException
注意:
- 在上述过程中的1)2)3)4)6)8)后边,都要去判断是否需要进行"解析"过程 ("解析"见 第四章 类加载机制)
- 类的加载过程只有向上的双亲委托,没有向下的查询和加载,假设是ext在E:\Java\jdk1.6\jre\lib\ext\*.jar下加载一个类,那么整个查询与加载的过程与app无关。
- 假设A加载成功了,那么该类就会缓存在当前的类加载器实例对象C中,key是(A,C)(其中A是类的全类名,C是加载A的类加载器对象实例),value是对应的java.lang.Class对象
- 上述的1)2)3)都是从相应的类加载器实例对象的缓存中进行查找
- 进行缓存的目的是为了同一个类不被加载两次
- 使用(A,C)做key是为了隔离类,假设现在有一个类加载器B也加载了A,key为(A,B),则这两个A是不同的A。这种情况怎么发生呢?
- 假设有custom1、custom2两个自定义类加载器,他们是兄弟关系,同时加载A,这就是有可能的了
总结:
- 从底向上检查是否加载过指定名称的类;从顶向下加载该类。(在其中任何一个步骤成功之后,都会中止类加载过程)
- 双亲委托的好处:假设自己编写了一个java.lang.Object类,编译后置于类路径下,此时在系统中就有两个Object类,一个是rt.jar的,一个是类路径下的,在类加载的过程中,当要按照全类名去加载Object类时,根据双亲委托,boot会加载rt.jar下的Object类,这是方法结束,即类路径下的Object类就没有加载了。这样保证了系统中类不混乱。
5、源代码
/** * 根据指定的binary name加载class。 * 步驟: * 假设我现在从类路径下加载一个类A, * 1)那么app会先查找是否加载过A(findLoadedClass(name)),若有,直接返回; * 2)若没有,去ext检查是否加载过A(parent.loadClass(name, false)),若有,直接返回; * findBootstrapClassOrNull(name) 3)4)5)都是这个方法 * 3)若没有,去boot检查是否加载过A,若有,直接返回; * 4)若没有,那就boot加载,若在E:\Java\jdk1.6\jre\lib\*.jar下找到了指定名称的类,则加载,结束; * 5)若没找到,boot加载失败; * findClass(name) 6)7)8)9)都是这个方法 * 在findClass中调用了defineClass方法,该方法会生成当前类的java.lang.Class对象 * 6)ext开始加载,若在E:\Java\jdk1.6\jre\lib\ext\*.jar下找到了指定名称的类,则加载,结束; * 7)若没找到,ext加载失败; * 8)app加载,若在类路径下找到了指定名称的类,则加载,结束; * 9)若没有找到,抛出异常ClassNotFoundException * 注意:在上述过程中的1)2)3)4)6)8)后边,都要去判断是否需要进行"解析"过程 */ protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name);//检查要加载的类是不是已经被加载了 if (c == null) {//没有被加载过 try { if (parent != null) { //如果父加载器不是boot,递归调用loadClass(name, false) c = parent.loadClass(name, false); } else {//父加载器是boot /* * 返回一个由boot加载过的类;3) * 若没有,就去试着在E:\Java\jdk1.6\jre\lib\*.jar下查找 4) * 若在bootstrap class loader的查找范围内没有查找到该类,则返回null 5) */ c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { //父类加载器无法完成加载请求 } if (c == null) { //如果父类加载器未找到,再调用本身(这个本身包括ext和app)的findClass(name)来查找类 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
说明:
- 该段代码中引用的大部分方法实质上都是native方法
- 其中findClass方法的类定义如下:
/** * 查找指定binary name的类 * 该类应该被ClassLoader的实现类重写 */ protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
- 关于findClass可以查看URLClassLoader.findClass(final String name),其中引用了defineClass方法,在该方法中将二进制字节流转换为了java.lang.Class对象。
附:关于递归
递归基于栈实现。
上述的代码如果不清楚递归的意义是看不清的。
解释:
- app_loadClass()方法执行到ext_loadClass(),这时候对于app_loadClass()中剩余的findClass()会在栈中向下压;
- 然后执行ext_loadClass(),当执行到findBootstrapClassOrNull(name),这时候ext_loadClass()中剩余的findClass()也会从栈顶向下压,此时ext_loadClass()_findClass()仅仅位于app_loadClass()_findClass()的上方;
- 然后执行findBootstrapClassOrNull(name),当boot检测过后并且执行完加载后并且没成功,boot方法离开栈顶;
- 然后执行此时栈顶的ext_loadClass()_findClass()
- 然后执行此时栈顶的app_loadClass()_findClass()
这样,就完成了双亲委托机制。
递归太烦了,实际开发中尽量不要用!
相关文章
- grunt源码解析:整体运行机制&grunt-cli源码解析
- Java接口源码--System和应用程序进程间通信
- 数仓工具—Hive源码之SQL解析AntlrWorks的使用(10)
- 备战CKA每日一题——第5天 | 手动调度、kube-scheduler调度器解析、源码浅析
- 微信小程序 - 最好用的多选功能,列表点击选中并且高亮显示(支持全选 / 反选,回显设置默认选择数据的功能,点击选中并改变样式,点击选中并高亮显示效果)详细教程功能源码组件插件
- Storm-源码分析- Multimethods使用例子
- hugegraph 数据加载工具loader源码解析
- 源码解析-- DolphinScheduler master server流程执行过程分析
- 源码解析 --skywalking agent 插件加载流程
- ucosiii内核源码解析---os_core.c
- Laravel开发:Laravel框架门面Facade源码分析
- Spring源码解析之bean加载(二)
- Spring源码解析(?)Spring使用构造函数实例分析
- Spring源码分析(六)FactoryBean 接口解析
- stm32 文件系统数据读写源码解析
- 基于BES2500芯片的低功耗蓝牙BLE游戏手柄解决方案源码解析
- 《Android 源码设计模式解析与实战》——第1章,第1.2节让程序更稳定、更灵活——开闭原则
- 《Android 源码设计模式解析与实战》——第2章,第2.9节运用单例模式
- 【Spring源码】 BeanFactory和FactoryBean是什么?
- Java小白进阶系列——ConcurrentHashMap源码解析文章总目录
- JDK集合源码之HashTable解析
- Android 数据库 ObjectBox 源码解析
- SwiftUI 特效之全屏烟花效果解析sks SKScene SKEmitterNode (教程含源码)
- SwiftUI 图表教程之 06 绘制折线图显示标题GeometryReader(教程含源码)
- SwiftUI 精品开源项目之 01 超酷全功能倒计时Counter App (教程含源码)
- SwiftUI 2.0 LazyVGrid和LazyHGrid 深入使用教程含源码
- Kivy教程之 08 倒计时App实现timer调用(教程含源码)
- SwiftUI Mapkit 导航基础教程大全之 MKMapView地图显示并实现导航线路(教程含源码)
- 《Python数据可视化之matplotlib实践》 源码 第四篇 扩展 第十章
- 多目标遗传算法 ------ NSGA-II (部分源码解析)父、子种群合并 merge.c
- Android实现手部检测和手势识别(可实时运行,含Android源码)
- 浅析 axios 的 onDownloadProgress 和 onUploadProgress 配置上传下载进度条以及 onUploadProgress 的源码解读
- Flink源码解析一之RPC原理解析
- Unity制作赛车游戏(无尽版)源码解析 和 Whell joint 2D组件的使用
- 【大数据】Hive SQL执行全过程源码解析(Hive3.1)
- [转]Spring源码解析之@Configuration
- 开源任务调度平台elastic-job-lite源码解析
- 曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)
- Laravel学习笔记之Session源码解析(下)
- Android MIFARE NFCA源码解析