前言

先解释一下什么是类加载器,通过一个类的全限定名来获取描述该类的二进制字节流,在虚拟机中实现这个动作的代码被称为“类加载器(Class Loader)”。

类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。每个类加载器都有一个独立的类名称空间,所以每个类唯一性都必须是建立在是否为同一个类加载器的前提下的。

否则,即使是两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

例如:

public class ClassLoaderOneTest {

    public static void main(String[] args) throws Exception{

        ClassLoader oneLoader = new ClassLoader() {

            @Override
public Class<?> loadClass(String name) throws ClassNotFoundException { try { String classFileName = name.substring(name.lastIndexOf(".")+1)+".class"; InputStream inputStream = getClass().getResourceAsStream(classFileName); if(inputStream == null){
return super.loadClass(name);
} byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); return defineClass(name,bytes,0,bytes.length);
}catch (IOException e){
throw new ClassNotFoundException(name); }
}
}; Object object = oneLoader.loadClass("com.eurekaclient2.test.jvm3.SonClass").newInstance(); System.out.println(object.getClass()); System.out.println("instanceof result :"+ (object instanceof com.eurekaclient2.test.jvm3.SonClass));
} }

运行结果:

class com.eurekaclient2.test.jvm3.SonClass
instanceof result :false

通过上面的运行结果可以看出,自定义的类加载器加载出来的类创建的对象和com.eurekaclient2.test.jvm3.SonClass在做类型检查时返回了false,这是因为在Java虚拟机中存在的两个SonClass,一个是由虚拟机的应用程序类加载器所加载的,另一个是由自定义的类加载器加载的,虽然来自同一个Class文件,但在Java虚拟机中是两个互相独立的类。

双亲委派模型

Java虚拟机把类加载器分为了两大类:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器是虚拟机的一部分。另外一种就是其他类加载器(全部继承自java.lang.ClassLoader)。

从JDK1.2开始至JDK9之前的Java应用绝大多数都会使用到如下3个系统提供的类加载器进行加载。

  • 启动类加载器(Bootstrap Class Loader):这个类加载器复制加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的类库加载到虚拟机的内存中。

    如果需要使用引导类加载器去加载类,直接使用null代替即可。

    如下是ClassLoader.getClassLoader()方法的源码:
/**
* Returns the class loader for the class. Some implementations may use
* null to represent the bootstrap class loader. This method will return
* null in such implementations if this class was loaded by the bootstrap
* class loader.
* /
@CallerSensitive
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
  • 扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。这是Java系统类库的扩展机制,但是在JDK9之后,被模块化能力所替代了。
  • 应用程序类加载器(Application Class Loader):这个类加载器由sun.misc.Launcher$AppClassLoader来实现的。它负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如没有自定义的类加载器,这个就是默认的类加载器。

在JDK9之前的Java应用都是由这三类加载器互相配合来完成加载的,如果有自定义的类加载器,会先执行自定义的类加载器。各种的类加载器之间的层次关系被称为类加载器的“双亲委派模型(Parents Delegation Model)”。

各种类加载器的关系如下图:



双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。

这里的加载器之间的子父关系不是继承,通常使用组合关系来复用父加载器的代码。

双亲委派模型并不是一个强制性约束力的模型而是Java设计者推荐给开发者的一种类加载器实现的最佳实践。

双亲委派模型的工作过程

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶端的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载才会尝试自己去完成加载。

使用双亲委派模型来组织类加载器之间的关系的好处就是能保证Java类型体系中最基础的类的唯一。

例如:类java.lang.Object无论哪一个类加载器加载最终都会委派给启动类加载器,因此能够保证各种类加载器环境中的都是同一个类。这样就能保证我们创建出来的类的拥有最基础的行为。

记得以前在面试的时候有被问到,让自己写一个java.lang.String类然后会被虚拟机加载运行吗?这个问题考察的就是类的加载机制的双亲委派模型。

双亲委派模型的源码其实非常简洁,先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的loadeClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父加载器加载失败,抛出ClassNotFoundException异常,才会调用自己的findClass方法尝试进行加载。

源码如下:

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求的类是否已经被加载过了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出ClassNotFoundException
// 说明父类加载器无法完成加载请求
} if (c == null) {
// 在父类加载器无法加载时
// 在调用本身的findClass方法来进行类加载
long t1 = System.nanoTime();
c = findClass(name); // this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

破坏双亲委派模型

上面也提到了,双亲委派模型并不是一个具体强制性约束的模型,虽然在Java的世界大部分的类加载器都遵循这个模型,但也有例外的情况,直到JDK9(模块化)为止,主要出现过3次较大规模“被破坏”的情况。

  • 第一次“被破坏”其实发生在双亲委派模型出现之前,由于双亲委派模型在JDK1.2之后才被引入,但是类加载器的概念和抽象类java.lang.ClassLoader则在Java的第一个版本中就已经存在了。为了向前兼容,只能在JDK1.2之后的java.lang.ClassLoader中添加一个新的protected方法findClass(),并引导用户编写类的加载逻辑时尽可能去重写这个方法,而不是在loadClass()中编写代码。
  • 第二次“被破坏”是因为自身的一些局限性导致的,双亲委派模型很好的解决了各个类加载器协作时基础类型的一致性问题,即越基础的类由越上层的加载器进行加载。

    但是如果基础的类需要调用下面的用户代码时该怎么办呢?Java设计团队用了一个不太优雅的方案,引入了一个名叫线程上下文的类加载器(Thread Context ClassLodar)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

    像JNDI、JDBC、JCE、JAXB、JBI等都是用的这种类型加载器实现的功能。最常见的tomcat中就用到了线程上下文的类加载器。
  • 第三次“破坏”,是为了实现热部署、模块化。在更新了一部分代码后,不需要停机重启,只需要将类加载器和类都替换掉就可以了。典型的就是OSGi的模块化热部署。

深入理解JVM(③)虚拟机的类加载器(双亲委派模型)的更多相关文章

  1. JVM探究之 —— 类加载器-双亲委派模型

    虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这个动作的代码模块称为“类加载器 ...

  2. 【深入理解JVM】:类加载器与双亲委派模型

    类加载器 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取定义此类的二进制字 ...

  3. JVM(16)之 双亲委派模型

    开发十年,就只剩下这套架构体系了! >>>   在上一篇博文中,我们知道了如何获得二进制的字节流,并根据获得的字节流去装载一个类.同时也了解到类加载器的存在,每个加载器对应着不同的加 ...

  4. 【深入理解Java虚拟机 】类加载器的命名空间以及类的卸载

    类加载器的命名空间 每个类加载器又有一个命名空间,由其以及其父加载器组成 类加载器的命名空间的作用和影响 每个类加载器又有一个命名空间,由其以及其父加载器组成 在每个类加载器自己的命名空间中不能出现相 ...

  5. 深入理解JVM虚拟机-2垃圾收集器

    这里讨论的收集器基于JDK 1.7 Update 14之后的HotSpot虚拟机. 如果两个收集器之间存在连线,说明可以搭配使用.虚拟机所处的区域,则表示它是属于新生代收集器还是年老代收集器.在这里我 ...

  6. JVM——类加载器的双亲委派模型

    类加载器双亲委派模型,如下图所示: 双亲委派模型的工作过程 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此 ...

  7. JVM类加载过程与双亲委派模型

    类加载过程 类加载过程为JVM将类描述数据从.class文件中加载到内存,并对数据进行解析和初始化,最终形成被JVM直接使用的Java类型.包含: 加载:获取该类的二进制字节流,将字节流代表的静态存储 ...

  8. 深入理解java虚拟机【类加载机制】

    Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程. 在加载阶段,java虚拟机需要完成以下 ...

  9. Java面试题之类加载器有哪些?什么是双亲委派模型

    类加载器有哪些: 1.启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或被-Xbootclasspath参数所指 ...

  10. Java的类加载器都有哪些,每个类加载器都有加载那些类,什么是双亲委派模型,是做什么的?

    类加载器按照层次,从顶层到底层,分为以下三种: (1)启动类加载器(Bootstrap ClassLoader) 这个类加载器负责将存放在JAVA_HOME/lib下的,或者被-Xbootclassp ...

随机推荐

  1. js 识别二维码

    本文引用analyticCode.js.llqrcode.js实现识别二维码功能 html代码: <div class="box" id="analytic&quo ...

  2. pycharm关联git

    一.先创建SSH Key 给github设置SSH-KEY !!! 这一步算是连接GitHub的最基本的一步了,git是分布式的代码管理工具,远程的代码管理是基于ssh的,所以得先配好SSH key. ...

  3. LeetCode 74,直击BAT经典面试题

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题43篇文章,我们今天来看一下LeetCode当中的74题,搜索二维矩阵,search 2D Matrix. 这题的 ...

  4. Java实现 LeetCode 746 使用最小花费爬楼梯(递推)

    746. 使用最小花费爬楼梯 数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 costi. 每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶 ...

  5. Java实现 蓝桥杯VIP 算法提高 林丹大战李宗伟

    问题描述 我们用0表示林丹,1表示李宗伟. 输入数据中每行会给出一个0或者1,表示对应选手得1分. 当一方得分达到21分时,只要该方与对方分差超过1分,该方即胜出. 你需要输出最后获胜选手的代号. 输 ...

  6. Java中输入时IO包与Scanner的区别

    最常用的一个IO控制台输入的 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream ...

  7. Java实现 蓝桥杯 算法提高 扶老奶奶过街

    1 问题描述 一共有5个红领巾,编号分别为A.B.C.D.E,老奶奶被他们其中一个扶过了马路. 五个红领巾各自说话: A :我和E都没有扶老奶奶 B :老奶奶是被C和E其中一个扶过大街的 C :老奶奶 ...

  8. Java实现第九届蓝桥杯乘积为零

    乘积为零 如下的10行数据,每行有10个整数,请你求出它们的乘积的末尾有多少个零? 5650 4542 3554 473 946 4114 3871 9073 90 4329 2758 7949 61 ...

  9. Spring Data Jpa Specification 调用Oracle 函数/方法

    开发框架用的Jpa,数据库是 Oracle. 在开发中难免会遇到需要数据库字段是字符串格式,但是又需要对其进行范围查询(数据库设计问题,后续应避免).那么问题来了, Jpa Specification ...

  10. matplotlib 示例

    示例1 import numpy as np import matplotlib.pyplot as plt #plt.rcParams['font.family'] = ['sans-serif'] ...