类加载器简介

在介绍双亲委托模型之前,先介绍一下类加载器。类加载器通过一个类的全限定名来转换为描述这个类的二进制字节流。

对于任意一个类,被同一个类加载器加载后都是唯一的,但如果被不同加载器加载后,就不是唯一的了。即使是源于同一个Class文件、被同一个JVM加载,只要加载类的加载器不同,那么类就不同。

如何判断类是否相同,可以使用Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果进行判断,也可以使用instanceof关键字进行对象所属关系的判断。
下面我们写一个不同类加载器加载后的类,看一下对instanceof关键字运算有什么影响:

public class OneMoreStudy {public static void main(String[] args) throws Exception {ClassLoader myLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {try {String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";InputStream inputStream = getClass().getResourceAsStream(fileName);if (inputStream == null) {return super.loadClass(name);}byte[] array = new byte[inputStream.available()];inputStream.read(array);return defineClass(name, array, 0, array.length);} catch (IOException e) {throw new ClassNotFoundException(name);}}};Object object = myLoader.loadClass("OneMoreStudy").newInstance();System.out.println("class name: " + object.getClass().getName());System.out.println("instanceof: " + (object instanceof OneMoreStudy));}
}

运行结果:

class name: OneMoreStudy
instanceof: false

在运行结果中,第一行可以看出这个对象确实是OneMoreStudy类实例化出来的,但在第二行中instanceof运算结果是false,说明在JVM中存在两个OneMoreStudy类,一个是由系统应用程序类加载器加载的,另一个是由我们自定义的类加载器加载的。虽然都是来自同一个Class文件,在同一个JVM里,但是被不同的类加载器加载后,仍然是两个独立的类。

类加载器的划分

除了像上面例子代码中,我们自己实现的自定义类加载器,还有3种系统提供的类加载器:

  1. 启动类加载器(Bootstrap ClassLoader):它负责将存放在%JAVA_HOME%\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是JVM识别的类库加载到JVM内存中。它仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载。它是由C++语言实现的,无法被Java程序直接引用。

  2. 扩展类加载器(Extension ClassLoader):它负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。它由sun.misc.Launcher.ExtClassLoader实现,开发者可以直接使用扩展类加载器。

  3. 应用程序类加载器(Application ClassLoader):它负责加载用户类路径(ClassPath)上所指定的类库。由于它是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它由sun.misc.Launcher.AppClassLoader来实现,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委托模型

之前提到,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在JVM中的唯一性。可是有这么多种的类加载器,如何保证一个类在JVM中的唯一性呢?为了解决这个问题,双亲委托模型(Parents Delegation Model)应运而生,它就是下图所展示的类加载器之间的层次关系:

除了顶层的启动类加载器外,其余的类加载器都必须有自己的父类加载器。类加载器之间的父子关系,一般不会以继承的关系来实现,而是都使用组合关系来复用父类加载器。

类加载器收到类加载的请求后,它不会首先自己去尝试加载这个类,而是把这个请求委派给父类加载器去尝试加载。每一个类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这样就保证了类在JVM中的唯一性,也保证了Java程序稳定运作。

实现双亲委派模型的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如下:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {//首先,检查该类是否已经被加载过了Class<?> c = findLoadedClass(name);//如果没有加载过,就调用父类加载器的loadClass()方法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);//记录统计数据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设计者们推荐给开发者们的类加载器实现方式。大部分的类加载器都遵循这个模型,但也有例外的情况,比如下面这三种情况:

重写ClassLoader的loadClass()方法

在上面例子代码中,就是重写了ClassLoader的loadClass()方法,破坏了双亲委派模型,产生了不唯一的类。所以,不提倡开发人员覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里如果父类加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派模型。

SPI(服务提供者接口)

Java提供了很多SPI(Service Provider Interface,服务提供者接口),允许第三方为这些接口提供实现,常见的SPI有JDBC、JNDI、JCE、JAXB和JBI等。

SPI的接口由Java核心库来提供,而这些SPI的实现代码则是作为Java应用所依赖的jar包被包含进类路径(ClassPath)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器来加载的;SPI的实现类是由系统类加载器来加载的。引导类加载器是无法找到SPI的实现类的,因为依照双亲委派模型,启动类加载器无法委派系统类加载器来加载类。

这时候就会使用线程上下文类加载器(Thread Context ClassLoader),在JVM中会把当前线程的类加载器加载不到的类交给线程上下文类加载器来加载,直接使用Thread.currentThread().getContextClassLoader()来获得,默认返回的就是应用程序类加载器,也可以通过java.lang.Thread类的setContextClassLoader()方法进行设置。

而线程上下文类加载器破坏了双亲委派模型,也就是父类加载器请求子类加载器去完成类加载的动作,但为了实现功能,这也是一种巧妙的实现方式。

OSGi(开放服务网关协议)

OSGi(Open Service Gateway Initiative,开放服务网关协议)技术是面向Java动态化模块化系统模型,程序模块(称为Bundle)无需重新引导可以被远程安装、启动、升级和卸载。实现程序模块热部署的关键则是它自定义的类加载器机制的实现。

在OSGi中,类加载器不再是双亲委派模型中的树状结构,而是一个较为复杂的网状结构,类加载的规则简要介绍如下:

  1. 若类属于java.*包,则将加载请求委托给父加载器
  2. 若类定义在启动委托列表(org.osgi.framework.bootdelegation)中,则将加载请求委托给父加载器
  3. 若类属于在Import-Package中定义的包,则框架通过ClassLoader依赖关系图找到导出此包的Bundle的ClassLoader,并将加载请求委托给此ClassLoader
  4. 若类资源属于在Require-Bundle中定义的Bundle,则框架通过ClassLoader依赖关系图找到此Bundle的ClassLoader,将加载请求委托给此ClassLoader
  5. Bundle搜索自己的类资源( 包括Bundle-Classpath里面定义的类路径和属于Bundle的Fragment的类资源)
  6. 若类在DynamicImport-Package中定义,则开始尝试在运行环境中寻找符合条件的Bundle

如果在经过上面一系列步骤后,仍然没有正确地加载到类资源,则会向外抛出类未发现异常。

总结

类加载器通过一个类的全限定名来转换为描述这个类的二进制字节流,可划分为启动类加载器扩展类加载器应用程序类加载器自定义类加载器。在双亲委托模型中,将上述各种类加载器组成一系列的父子关系,子类加载器先把类加载请求委派给父类加载器去尝试加载,父类加载器无法加载时子类加载器才自己尝试加载,这样保证了类在JVM中的唯一性。不过,也不遵循双亲委托模型的情况,比如:重写ClassLoader的loadClass()方法、SPI(服务提供者接口)、OSGi(开放服务网关协议)。

月薪过万必会的:双亲委托模型相关推荐

  1. Java类加载机制:双亲委托模型

    Java类加载机制:双亲委托模型 前言(废话) 一如既往,这篇博客是我极为浅显的理解,仅仅是我记录我自己成长的一环而已.我以前听我老师说过,什么是进步,进步就是当你三个月后重新再看自己的代码,发现那就 ...

  2. java 整合hadoop_Spark 如何摆脱java双亲委托机制优先从用户jar加载类?

    1. 起源 spark的类加载及参数传递过程还是很复杂的,主要是因为他运行环境太复杂了,不同的集群管理器完全不一样,即使是同一集群管理器cluster和client也不一样,再加上这块探究还是需要一定 ...

  3. 类加载机制:全盘负责和双亲委托

    全盘负责 全盘负责是指当一个ClassLoader加载一个类时,除非显示地使用另一个ClassLoader,则该类所依赖与引用的类也由这个ClassLoader加载. 例如:系统类加载器AppClas ...

  4. 干货!!月薪过万行业,软件测试必懂的基本概念

    一.软件测试的定义: 用来促进鉴定软件的正确性.完整性.安全性和质量的过程.换句话说,软件测试是一种实际输出与预期输出之间的审核或者比较的过程. 软件测试的经典定义是:在规定的条件下对程序进行操作,以 ...

  5. 测试想要月薪过万?这些能力必不可少!

    月薪过万这个话题,在现在这个百花齐放的职场里是个很流行的命题. 月薪过万对于行业大佬来说,可能是个不屑一顾的追求,但对于职场新人而言通常是个很实际的人生目标. 我怎么样能达到月薪过万呢,其实严格来说, ...

  6. 月薪过万的测试员,是一种什么样的生活状态?你愿意再战一次吗...

    在北上广深月薪过万的测试员的生活真的和大城市一样绚烂吗? 前几天笔者在知乎上看到了这么一则问题,在人均过万的社交平台现状中,评论区的回答各具特点,笔者在其中也找到了不少真挚的回答. 对于测试员而言,月 ...

  7. 什么水平的java工程师月薪3万起?

    什么水平的java工程师月薪3万起?首先java基本功需要具备,所谓的基本功,不是简单的能写出代码,除了能写出来之外还要,在代码质量上面需要具体一定体现,比如对一些简单的多线程,以及常见的java框架 ...

  8. Java开发月薪两万,需要达到怎样的技术水平?

     Java开发月薪两万,需要达到怎样的技术水平? 首先两万的月薪在BAT实在太普遍了,一般是高级工程师和资深工程师的职位.在阿里是p6~p7左右,在百度是t5左右,腾讯是t2-3左右,京东是t3-1, ...

  9. UI设计从业者,怎样才能成为月薪过万的UI设计师?

    初级UI设计师和高级UI设计师有什么不同?其实最主要的不同就在于设计技能和薪资待遇不同,很多刚入行的小伙伴们都想成为一名月薪过万的UI设计师,那么想要成为一名高级UI设计师需要满足哪些条件?下面牛耳教 ...

最新文章

  1. 初级开发人员的缺点_作为一名初级开发人员,我如何努力克服自己的挣扎
  2. PowerPC VxWorks BSP分析7——image压缩
  3. 刘知远 陈慧敏:流言止于“智”者——网络虚假信息的特征与检测
  4. python面试题之“该死的for循环系列”(二)
  5. 5月26 留言板练习题
  6. 远程断开远程桌面会话之方法
  7. 分布式文件系统研究-什么是分布式文件系统
  8. idea取消comiit_IDEA 合并多次commit为一个?
  9. aws ec2时间_AWS中自动化的三大领域,以避免支付过多的云账单
  10. 暖心!湖北伢雷军为家乡再捐1270万 大批紧缺医护物资连夜运抵武汉
  11. 手机运行速度慢怎么办
  12. sklearn的快速使用
  13. EasyVS -- 快速整理region的Visual Studio扩展
  14. ST 电机库 电机位置环7天冲刺开发
  15. 区块链 之 以太坊的那些坑
  16. kinit什么意思_kerberos入坑指南
  17. java qq邮箱登录_SpringBoot实现QQ邮箱注册和登录
  18. 计算机无法连接网络错误651,电脑宽带连接651错误的解决方法
  19. python兔子_python学习:关于生兔子问题
  20. 什么是数据挖掘?数据挖掘的目标是什么?

热门文章

  1. 少儿编程入门001,在家自己带孩子学编程
  2. 动态库和静态库的生成
  3. Xamarin.Android之绑定库教程
  4. Redis学习笔记(实战篇)(自用)
  5. 计算机与音乐教育留学,计算机与儿童创新音乐教育
  6. 详细的辅助开发教程,从入门到精通
  7. 咋阻止别人用计算机监控我家,我想用我家里的电脑控制办公室电脑的打印机怎么处理?...
  8. 电脑端如何隐藏显示CAD图层内容
  9. 23-1-18 PDManer 工具
  10. LED电性能IV扫描测试方案