热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java类加载器双亲委派,类加载器与双亲委派机制

目录一、类与类加载器类加载器用于实现类的加载,加载器会把载入内存中的类生成一个java.lang.Class实例对象。对于任意一个类,都必须由加载它的类

目录

一、类与类加载器

类加载器用于实现类的加载,加载器会把载入内存中的类生成一个java.lang.Class实例对象。对于任意一个类, 都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。也就是说:比较两个类是否“相等”, 只有在这两个类是由同一个类加载器加载的前提下才有意义, 否则, 即使这两个类来源于同一个Class文件, 被同一个Java虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等。下面是类加载器对instanceof关键字运算的结果的影响:

package com.me.jvm;

import java.io.IOException;

import java.io.InputStream;

public class ClassLoaderTest {

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

ClassLoader loader = new ClassLoader() {

@Override

public Class> loadClass(String name) throws ClassNotFoundException {

try {

String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";

InputStream is = getClass().getResourceAsStream(fileName);

if (is == null) {

return super.loadClass(name);

}

byte[] b = new byte[is.available()];

is.read(b);

return defineClass(name, b, 0, b.length);

} catch (IOException e) {

throw new ClassNotFoundException(name);

}

}

};

Object obj = loader.loadClass("com.me.jvm.ClassLoaderTest").newInstance();

System.out.println(obj.getClass());

System.out.println(obj instanceof ClassLoaderTest);

System.out.println(obj.getClass().getClassLoader());

System.out.println(ClassLoaderTest.class.getClassLoader());

/**

* 打印结果:

* class com.me.jvm.ClassLoaderTest

* false

* com.me.jvm.ClassLoaderTest$1@74a14482

* sun.misc.Launcher$AppClassLoader@18b4aac2

*/

}

}

上面的示例显示:在自定义的classLoader去加载了一个名为“com.me.jvm.ClassLoaderTest”的类, 并实例化了这个类的对象。前两行输出结果中, 从第一行可以看到这个对象确实是类com.me.jvm.ClassLoaderTest实例化出来的, 但在第二行的输出中却发现这个对象与类com.me.jvm.ClassLoaderTest做所属类型检查的时候返回了false。 这是因为Java虚拟机中同时存在了两个ClassLoaderTest类, 一个是由虚拟机的应用程序类加载器所加载的(参考第四行结果), 另外一个是由我们自定义的类加载器加载的(参考第三行结果), 虽然它们都来自同一个Class文件, 但在Java虚拟机中仍然是两个互相独立的类。

二、三层类加载器

本节内容将针对JDK 8及之前版本的Java来介绍什么是三层类加载器。正如我们通常认为的那样,绝大多数Java程序都会使用到以下3个系统提供的类加载器来进行加载。分别是:启动类加载器、扩展类加载器、应用程序类加载器。贴出如下代码来查看各层加载器及其父类加载器:

/*示例1:输出加载器的父类加载器*/

Object obj = loader.loadClass("com.me.jvm.ClassLoaderTest").newInstance();//loader为第一节中自定义加载器

//1.1

System.out.println(obj.getClass().getClassLoader());//com.me.jvm.ClassLoaderTest$1@74a14482

//1.2

System.out.println(ClassLoaderTest.class.getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2

//1.3

System.out.println(ClassLoaderTest.class.getClassLoader().getParent());//sun.misc.Launcher$ExtClassLoader@14ae5a5

//1.4

System.out.println(ClassLoaderTest.class.getClassLoader().getParent().getParent());//null

/*示例2:输出各个类的加载器*/

//2.1

System.out.println(ClassLoaderTest.class.getClassLoader());//sun.misc.Launcher$AppClassLoader

//2.2

System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getClassLoader());//null

//2.3

System.out.println(com.sun.nio.zipfs.ZipPath.class.getClassLoader());//sun.misc.Launcher$ExtClassLoader@14ae5a5

//2.4

System.out.println(com.sun.nio.zipfs.ZipPath.class.getClassLoader().getClass().getClassLoader());//null

从示例一中可以看出,其中三层加载器及自定义加载器是相互补充依赖,并不是继承关系,查看源码的话可以看出加载器ClassLoader类里有一个final修饰的ClassLoader类型的parent属性。加载器的依赖顺序是:自定义加载器的-->应用程序类加载器(AppClassLoader)-->扩展类加载器(ExtClassLoader)-->启动类加载器(null)。从示例二中可以看出,各层加载器加载的类是不一样的。下面是三层加载器的说明 :

·启动类加载器(Bootstrap Class Loader)

这个类加载器负责加载存放在\lib目录, 或者被-Xbootclasspath参数所指定的路径中存放的, 而且是Java虚拟机能够识别的(按照文件名识别, 如rt.jar、 tools.jar, 名字不符合的类库即使放在lib目录中也不会被加载) 类库加载到虚拟机的内存中。 启动类加载器无法被Java程序直接引用, 用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理, 那直接使用null代替即可( null值来代表引导类加载器的约定规则,可参考上面示例2.2或2.4)。

·扩展类加载器(Extension Class Loader) :

这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。 它负责加载\lib\ext目录中, 或者被java.ext.dirs系统变量所指定的路径中所有的类库。 根据“扩展类加载器”这个名称, 就可以推断出这是一种Java系统类库的扩展机制, JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能, 在JDK9之后, 这种扩展机制被模块化带来的天然的扩展能力所取代。 由于扩展类加载器是由Java代码实现的, 开发者可以直接在程序中使用扩展类加载器来加载Class文件(可参考上面示例2.3)。

·应用程序类加载器(Application Class Loader)/系统类加载器 :

这个类加载器由sun.misc.Launcher$AppClassLoader来实现。 由于应用程序类加载器是ClassLoader类中的getSystemClassLoader()方法的返回值, 所以有些场合中也称它为“系统类加载器”。 它负责加载用户类路径(ClassPath) 上所有的类库, 开发者同样可以直接在代码中使用这个类加载器。 如果应用程序中没有自定义过自己的类加载器, 一般情况下这个就是程序中默认的类加载器。(可参考上面示例2.1)。

三、双亲委派机制

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

加载请求最终都应该传送到最顶层的启动类加载器中, 只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类) 时, 子加载器才会尝试自己去完成加载。

使用双亲委派模型来组织类加载器之间的关系, 一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。 例如类java.lang.Object, 它存放在rt.jar之中, 无论哪一个类加载器要加载这个类, 最终都是委派给处于模型最顶端的启动类加载器进行加载, 因此Object类在程序的各种类加载器环境中都能够保证是同一个类。 反之, 如果没有使用双亲委派模型, 都由各个类加载器自行去加载的话, 如果用户自己也编写了一个名为java.lang.Object的类, 并放在程序的ClassPath中, 那系统中就会出现多个不同的Object类, Java类型体系中最基础的行为也就无从保证, 应用程序将会变得一片混乱。双亲委派模型对于保证Java程序的稳定运作极为重要, 但它的实现却异常简单,全部集中在java.lang.ClassLoader的loadClass()方法之中, 如代码如下:

protected Class> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

synchronized (getClassLoadingLock(name)) {

// 1、查看是否已经加载过此类

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;

}

}

这段代码的逻辑: 先检查请求加载的类型是否已经被加载过, 若没有则调用父加载器的loadClass()方法, 若父加载器为空则默认使用启动类加载器作为父加载器。 假如父类加载器加载失败,抛出ClassNotFoundException异常的话, 才调用自己的findClass()方法尝试进行加载。

下面是类加载器加载流程图:

4c3adb1fe33cd9ccda9fb880f2d282aa.png

四、总结

本文所用jdk版本为java1.8.0_201,以下是总结:

自定义加载器可以通过重写ClassLoader的loadClass方法来实现。

class都是通过classloader来装载的。

只有当你使用该class的时候才会去装载。

同一个加载器只会装载相同的class一次;同一个Java虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等。

双亲委派机制保证java运行安全稳定。

参考:《深入理解Java虚拟机:JVM高级特性与最佳实践》

本文地址:https://blog.csdn.net/changlina_1989/article/details/112483881

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!



推荐阅读
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • AFNetwork框架(零)使用NSURLSession进行网络请求
    本文介绍了AFNetwork框架中使用NSURLSession进行网络请求的方法,包括NSURLSession的配置、请求的创建和执行等步骤。同时还介绍了NSURLSessionDelegate和NSURLSessionConfiguration的相关内容。通过本文可以了解到AFNetwork框架中使用NSURLSession进行网络请求的基本流程和注意事项。 ... [详细]
author-avatar
小小一株含羞草2010
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有