前言

先解释一下什么是类加载器,通过一个类的全限定名来获取描述该类的二进制字节流,在虚拟机中实现这个动作的代码被称为“类加载器(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. Java分层经验

    在学习和使用Java的过程中,我们时常要用到各种工具与技术,它们在某些时候可以大幅度地简化编程,利用好它们,可以让代码更强壮.下面的表格是我总结的关于java开发可能会用到的工具与它们在项目中扮演的角 ...

  2. css 段落文字换行问题

    项目中遇到的一个小问题,以前没有注意到: 超链接超出父级元素,想着给a标签加宽度但是没有效果... 后来发现两个很好用的css属性 1.word-wrap 用来控制换行 取值: (1)normal  ...

  3. ASP.NET的Web网页如何进行分页操作(Demo举例)

    大概说一下思路,可以利用sql的 Offset/Fetch Next分页,点击这里 这里的Demo利用LINQ的写好的方法 //这里是某个表的列表 skip是跳过前面的多少条数据 take这是跳过前面 ...

  4. Java实现 蓝桥杯 算法提高 小X的购物计划

    试题 算法提高 小X的购物计划 问题描述 小X打算去超市shopping.小X没什么钱,只有N元.超市里有M种物品,每种物品都需要money,在小X心中有一个重要度.有的物品有无限件,有的物品只有几件 ...

  5. Java实现 LeetCode 380 常数时间插入、删除和获取随机元素

    380. 常数时间插入.删除和获取随机元素 设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构. insert(val):当元素 val 不存在时,向集合中插入该项. remove( ...

  6. Java实现 LeetCode 36 有效的数独

    36. 有效的数独 判断一个 9x9 的数独是否有效.只需要根据以下规则,验证已经填入的数字是否有效即可. 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在 ...

  7. Linux 文件特殊权限-SetUID

    SetUID非常类似于Windows中以管理员身份来运行文件,针对的是可执行文件,而且命令执行者要拥有对这个文件的执行权限,只在文件执行的过程中变换身份,最常见的passwd命令就具有SetUID权限 ...

  8. hibernate 用注解方式生成uuid方法

    //配置uuid,本来jpa是不支持uuid的,但借用hibernate的方法可以实现. @GeneratedValue(generator = "uuid") @Generate ...

  9. 【1】Vim 进阶操作

    一.标签 :tabnew one.c 新建标签[♥] 常用 :tabc 关闭文件   :tabp 切换前一个页面   :tabn 切换下一个页面   gt 普通模式下操作 常用 二.窗口 :sp 水平 ...

  10. 白嫖永久免费云服务器教程,永久免费虚拟主机、永久免费云数据库、搭建FTP服务器、服务器安装Linux / windows操作系统、服务器部署网站、宝塔一键部署多网站、独立ip、永久国内高速云服务器

    一.准备工作 1. 注册账号 声明:切记不可用服务器做违法的事情 申请地址:https://www.sanfengyun.com/ 图文教程地址:https://www.cnblogs.com/zwn ...