说明:了解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()

这样,就完成了双亲委托机制。

注意点:

类隔离机制

  1. 同一个类Dog可以加载两次(只要loader1和loader3不是父子关系即可,加载出的 Class 对象不同),不同运行空间内的类不能互相访问(eg. loader1和loader3不是父子关系,则Loader1加载的Dog不能访问lodaer3加载的Sample)
  2. 父类加载器无法访问到子类加载器加载的类,除非使用反射。Eg. Loader1 的父加载器是 系统类加载器,假设 Sample 类由 loader1 加载, 使用 loader1 的类 Test 是由系统类加载器加载的,例如下面这段代码属于 Test 类,那么如果直接使用注释部分的代码(即通过常规的方式使用 Sample 是不行的),必须通过反射。

第五章 类加载器ClassLoader源码解析的更多相关文章

  1. 类加载器ClassLoader源码解析

    1.ClassLoader作用 类加载流程的"加载"阶段是由类加载器完成的. 2.类加载器结构 结构:BootstrapClassLoader(祖父)-->ExtClassL ...

  2. JVM 类加载器ClassLoader源码学习笔记

    类加载 在Java代码中,类型的加载.连接与初始化过程都是在程序运行期间完成的. 类型可以是Class,Interface, 枚举等. Java虚拟机与程序的生命周期 在如下几种情况下,Java虚拟机 ...

  3. DRF-解析器组件源码解析

    解析器组件源码解析 解析器组件源码解析 1 执行request.data 开始找重装的request中的data方法 2 在dispatch找到重装的request def dispatch(self ...

  4. 第二章 Google guava cache源码解析1--构建缓存器

    1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHashMap(或者说成就是一个ConcurrentHashMap,只是在其上多添加了一些功能) ...

  5. Spring系列(五):Spring AOP源码解析

    一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ...

  6. 1.1 jvm核心类加载器--jdk源码剖析

    目录 前提: 运行环境 1. 类加载的过程 1.1 类加载器初始化的过程 1.2 类加载的过程 1.3 类的懒加载 2. jvm核心类加载器 3. 双亲委派机制 4. 自定义类加载器 5. tomca ...

  7. Flink Sql 之 Calcite Volcano优化器(源码解析)

    Calcite作为大数据领域最常用的SQL解析引擎,支持Flink , hive,  kylin , druid等大型项目的sql解析 同时想要深入研究Flink sql源码的话calcite也是必备 ...

  8. spark内存管理器--MemoryManager源码解析

    MemoryManager内存管理器 内存管理器可以说是spark内核中最重要的基础模块之一,shuffle时的排序,rdd缓存,展开内存,广播变量,Task运行结果的存储等等,凡是需要使用内存的地方 ...

  9. 类加载器 - ClassLoader详解

    获得ClassLoader的途径 获得当前类的ClassLoader clazz.getClassLoader() 获得当前线程上下文的ClassLoader Thread.currentThread ...

随机推荐

  1. JavaScript对象参考手册

    1.array 属性: constructor 返回原型函数: length 数组个数: prototype 向对象添加属性和方法 方法: concat() 连接两个或多个数组,并返回结果: fill ...

  2. Ionic Js四:复选框

    ionic 复选框(checkbox)与普通的 HTML 复选框没什么区别,以下实例演示了 ionic 复选框 ion-checkbox 的应用. <ion-checkbox ng-model= ...

  3. Python 爬虫个人记录(一)豆瓣电影250

    一.爬虫环境 Python3.6 scrapy1.4 火狐浏览器 qq浏览器 二.scrapy shell 测试并获取 xpath 1.进入scrapy shell 2 .获取html fetch(' ...

  4. PHP程序员未来路在何方

    PHP 从诞生到现在已经有20多年历史,从Web时代兴起到移动互联网退潮,互联网领域各种编程语言和技术层出不穷, Node.js . GO . Python 不断地在挑战 PHP 的地位.这些技术的推 ...

  5. 【SQL】181. Employees Earning More Than Their Managers

    The Employee table holds all employees including their managers. Every employee has an Id, and there ...

  6. [BZOJ3779]重组病毒(LCT+DFS序线段树)

    同[BZOJ4817]树点涂色,只是多了换根操作,分类讨论下即可. #include<cstdio> #include<algorithm> #define lc ch[x][ ...

  7. 2809: [Apio2012]dispatching 可并堆 左偏树

    https://www.lydsy.com/JudgeOnline/problem.php?id=2809 板子题wa了一下因为输出ans没有lld #include<iostream> ...

  8. python开发_thread_线程基础

    说到线程,我们要知道啥是串行,啥是并行程序 举个例子: 串行程序,就是一个一个的执行程序 #python threading import time ''' 每一秒中,输出:this is a dem ...

  9. 【XPath Helper:chrome爬虫网页解析工具 Chrome插件】XPath Helper:chrome爬虫网页解析工具 Chrome插件下载_教程_安装 - 开发者插件 - Chrome插件网

    [XPath Helper:chrome爬虫网页解析工具 Chrome插件]XPath Helper:chrome爬虫网页解析工具 Chrome插件下载_教程_安装 - 开发者插件 - Chrome插 ...

  10. 【Go入门教程8】interface(interface类型、interface值、空interface{}、嵌入interface、反射)

    interface Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计所折服. 什么是interface 简单 ...