第五章 类加载器ClassLoader源码解析
说明:了解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对象。
采用模板模式,我们实现自定义类加载器:
public class UserDefineClassLoader extends ClassLoader {
/**
* 自定义加载器的名称
*/
private String loaderName; /**
* 指定自定义加载器的名称
*/
public UserDefineClassLoader(String loaderName) {
// 父类加载器 this(checkCreateClassLoader(), getSystemClassLoader())
super();
this.loaderName = loaderName;
} /**
* 指定父类加载器
*/
public UserDefineClassLoader(String loaderName, ClassLoader parent) {
super(parent);
this.loaderName = loaderName;
} @Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 1. 读取文件内容为byte[]
byte[] classBytes = readClassDataFromFile("/Users/jigangzhao/Desktop/A.class");
// 2. 将byte[]转化为Class
Class<?> aClass = defineClass("classLoader.A", classBytes, 0, classBytes.length);
return aClass;
}
}
附:关于递归
递归基于栈实现。
上述的代码如果不清楚递归的意义是看不清的。
解释:
- 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()
这样,就完成了双亲委托机制。
注意点:
类隔离机制
- 同一个类Dog可以加载两次(只要loader1和loader3不是父子关系即可,加载出的 Class 对象不同),不同运行空间内的类不能互相访问(eg. loader1和loader3不是父子关系,则Loader1加载的Dog不能访问lodaer3加载的Sample)
- 父类加载器无法访问到子类加载器加载的类,除非使用反射。Eg. Loader1 的父加载器是 系统类加载器,假设 Sample 类由 loader1 加载, 使用 loader1 的类 Test 是由系统类加载器加载的,例如下面这段代码属于 Test 类,那么如果直接使用注释部分的代码(即通过常规的方式使用 Sample 是不行的),必须通过反射。
第五章 类加载器ClassLoader源码解析的更多相关文章
- 类加载器ClassLoader源码解析
1.ClassLoader作用 类加载流程的"加载"阶段是由类加载器完成的. 2.类加载器结构 结构:BootstrapClassLoader(祖父)-->ExtClassL ...
- JVM 类加载器ClassLoader源码学习笔记
类加载 在Java代码中,类型的加载.连接与初始化过程都是在程序运行期间完成的. 类型可以是Class,Interface, 枚举等. Java虚拟机与程序的生命周期 在如下几种情况下,Java虚拟机 ...
- DRF-解析器组件源码解析
解析器组件源码解析 解析器组件源码解析 1 执行request.data 开始找重装的request中的data方法 2 在dispatch找到重装的request def dispatch(self ...
- 第二章 Google guava cache源码解析1--构建缓存器
1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHashMap(或者说成就是一个ConcurrentHashMap,只是在其上多添加了一些功能) ...
- Spring系列(五):Spring AOP源码解析
一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ...
- 1.1 jvm核心类加载器--jdk源码剖析
目录 前提: 运行环境 1. 类加载的过程 1.1 类加载器初始化的过程 1.2 类加载的过程 1.3 类的懒加载 2. jvm核心类加载器 3. 双亲委派机制 4. 自定义类加载器 5. tomca ...
- Flink Sql 之 Calcite Volcano优化器(源码解析)
Calcite作为大数据领域最常用的SQL解析引擎,支持Flink , hive, kylin , druid等大型项目的sql解析 同时想要深入研究Flink sql源码的话calcite也是必备 ...
- spark内存管理器--MemoryManager源码解析
MemoryManager内存管理器 内存管理器可以说是spark内核中最重要的基础模块之一,shuffle时的排序,rdd缓存,展开内存,广播变量,Task运行结果的存储等等,凡是需要使用内存的地方 ...
- 类加载器 - ClassLoader详解
获得ClassLoader的途径 获得当前类的ClassLoader clazz.getClassLoader() 获得当前线程上下文的ClassLoader Thread.currentThread ...
随机推荐
- Python爬虫个人记录(三)爬取妹子图
这此教程可能会比较简洁,具体细节可参考我的第一篇教程: Python爬虫个人记录(一)豆瓣250 Python爬虫个人记录(二)fishc爬虫 一.目的分析 获取煎蛋妹子图并下载 http://jan ...
- Leaving Auction CF 749D
题目:http://codeforces.com/problemset/problem/749/D 题目大意: 有n个人竞拍,也有n个叫牌,一个人可以有多个叫价牌,但也可能有一些人根本不叫价 每个叫牌 ...
- mongodb的yum源配置和安装
安装前注意: 此教程是通过yum安装的.仅限64位centos系统 安装步骤: 1.创建仓库文件: vi /etc/yum.repos.d/mongodb-org-3.4.repo 然后复制下面配置, ...
- IE访问历史记录恢复工具pasco
IE访问历史记录恢复工具pasco IE浏览器会自动记录用户访问网站的操作,并将这些信息保存在IE缓存中的index.dat文件中.通过分析这些历史记录,可以还原用户行为.为了便于数字取证,Kali ...
- 使用gSOAP工具生成onvif框架代码
<工具产生背景> 由于SOAP是一种基于xml的文件,手动编写SOAP文件太机械耗时,在这种背景下产生了gSAOP 这个工具,用于生成各种类型的代码,目前支持C/C++, ...
- python列表中中文编码的问题
在python2列表中,有时候,想打印一个列表,会出现如下显示: 这个是由于: print一个对象,是输出其“为了给人(最终用户)阅读”而设计的输出形式,那么字符串中的转义字符需要转出来,而且 也不要 ...
- 决策树算法(ID3)
Day Outlook Temperature Humidity Wind PlayTennis 1 Sunny Hot High Weak No 2 Sunny Hot High Strong No ...
- Xshell6和Xftp下载地址,rzsz的使用
官方下载地址:https://cdn.netsarang.net/98f59c09/Xshell-6.0.0076r_beta.exe https://cdn.netsarang.net/98f59c ...
- 希尔排序之C++实现(高级版)
希尔排序之C++实现(高级版) 一.源代码:ShellSortHigh.cpp /*希尔排序基本思想: 先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组. 所有距离为d1的倍数的记录放在同 ...
- BZOJ 3572: [Hnoi2014]世界树 虚树 树形dp
https://www.lydsy.com/JudgeOnline/problem.php?id=3572 http://hzwer.com/6804.html 写的时候参考了hzwer的代码,不会写 ...