热门标签 | 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太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
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社区 版权所有