首页 > 编程语言 >Java类加载器

Java类加载器

时间:2024-10-04 10:44:42浏览次数:5  
标签:委派 Java 虚拟机 ClassLoader Class 加载

书接上回。。

3. 类加载器

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

3.1 类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。

这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只
要加载它们的类加载器不同,那这两个类就必定不相等。

这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。如果没有注意到类加载器的影响,在某些情况下可能会产生具有迷惑性的结果。

/**
 * @author strind
 * @date 2024/10/4 9:55
 * @description 不同类加载器对 instanceof 关键字运算结果的影响
 */
public class ClassLoaderTest {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader classLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream in = getClass().getResourceAsStream(fileName);
                    if (in == null) {
                        return super.loadClass(name);
                    }

                    byte[] b = new byte[in.available()];
                    in.read(b);
                    return defineClass(name,b,0,b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj = classLoader.loadClass("lianxi.ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof lianxi.ClassLoaderTest);

        /**
         * 运行结果
         * class lianxi.ClassLoaderTest
         * false

         * 两行输出结果中,从第一行可以看到这个对象确实是类lianxi.ClassLoaderTest实例化出来的,
         * 但在第二行的输出中却发现这个对象与类lianxi.ClassLoaderTest做所属类型检查的时候返回了false。
         * 这是因为Java 虚拟机中同时存在了两个ClassLoaderTest类,一个是由虚拟机的应用程序类加载器所加载的,
         * 另一个是由我们自定义的类加载器加载的,虽然它们都来自同一个Class文件,但在Java虚
         * 机中仍然是两个互相独立的类,做对象所属类型检查时的结果自然为false。
         */
    }

}

3.2 双亲委派模型

站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++语言实现,是虚拟机自身的一部分;另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

站在Java开发人员的角度来看,类加载器就应当划分得更细致一些。自JDK12以来
Java 一直保持着三层类加载器、双亲委派的类加载架构,尽管这套架构在Java模块化系统
出现后有了一些调整变动,但依然未改变其主体结构。

现主要介绍JDK8及之前版本的三层类加载器,以及什么是双亲委派模型。对于这个时期的Java应用,绝大多数Java程序都会使用到以下3个系统提供的类加载器来进行加载。

  1. 启动类加载器(Bootstrap ClassLoader):前面已经介绍过,这个类加载器负责将存放在JAVA_HOME\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的类库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可。
  2. 扩展类加载器(Extension Class Loader):这个类加载器是在类 sun.misc.LauncherSExtClassLoader中以Java代码的形式实现的。它负责加载JAVA _HOME\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。根据“扩展类加载器”这个名称,就可以推断出这是一种Java系统类库的扩展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展JavaSE的功能,在JDK9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。由于拓展类加载器是Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。
  3. 应用程序类加载器(Application Class Loader):这个类加载器由 sun.misc.LaunchersAppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

JDK9之前的Java应用都是由这三种类加载器互相配合来完成加载的,如果用户认为有必要,还可以加入自定义的类加载器来进行拓展,典型的如增加除了磁盘位置之外的Class文件来源,或者通过类加载器实现类的隔离、重载等功能。

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合关系来复用父加载器的代码。

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

使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object、它存放在rt.jar 之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类。

反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。

标签:委派,Java,虚拟机,ClassLoader,Class,加载
From: https://www.cnblogs.com/strind/p/18446419

相关文章

  • [Java并发]AQS的可重入性
    在Java中,AQS(AbstractQueuedSynchronizer,抽象队列同步器)通过设计一个独占和共享的同步机制,提供了可重入锁的实现。AQS的可重入性主要依赖于它对线程状态的跟踪。具体来说,可重入性是指同一个线程在获得锁之后可以多次进入(加锁多次),而不引发死锁。这是通过一个“重入计数器”来实现的......
  • [JavaScript] this 关键字
    全局作用域在浏览器中,如果在全局作用域下使用this,它将指向window对象;在Node.js环境中,则指向global对象。方法调用当一个函数作为对象的方法被调用时,this会指向该对象。constobj={name:"Alice",greet:function(){console.log(`Hello,${this.name}`)......
  • 从 JavaScript 到 OCaml:浅浅浅总结一下函数式编程
    背景这几天突击了一下Cornell的cs3110;抽了两个下午刷完了Chapter3,4,5的课后习题,很有感触。结合自己浅薄的函数式编程理解和贫瘠的JavaScript/TypeScript开发经历,总结一下自己第一阶段的函数式编程学习经历。......
  • java日总结24-10-3:mysql的基础知识
    今日学习javaweb1、了解了javaweb的概况与数据库的相关概念2、安装了mysqlMySQL的学习:1、SQL的简介:是一门操作关系型数据库的编程语言2、SQL的通用语法:单行注释:--注释内容或#注释内容;多行注释:/注释内容/3、SQL的分类:一、DDL操作数据库:创建数据库:判断是否存在创建:c......
  • 盘点五大热门JavaScript图表库,助你高效开发!
    1、Chart.jsChart.js是一个简单、灵活的图表库,支持8种图表类型(如折线图、柱状图、饼图等)。它使用HTML5Canvas元素来渲染图表,并且有良好的文档和社区支持。使用方法:npminstallchart.jsvue-chartjs然后在你的Vue组件中使用:<template><div><line-chart:data......
  • 掌握防抖与节流:如何用JavaScript封装通用函数
    在日常前端开发中,我们经常会遇到一些频繁触发的事件,如窗口调整大小、滚动条滚动、输入框输入等。为了提高页面性能和用户体验,我们需要对这些事件进行优化。本文将介绍如何使用JavaScript封装通用的防抖和节流函数。一、什么是防抖(Debounce)和节流(Throttle)?防抖(Debounce):当持续......
  • Java面向对象第四章方法重写与多态练习题
    练习1:使用重写优化电子宠物系统需求说明使用方法重写优化电子宠物系统,实现如下效果 packagecom.hz.ch02;/***猫类*@author26255**/publicclassCatextendsFather{ privateStringsex; publicCat(Stringname,inthealth,intlove,Stringse......
  • 虚拟机类加载机制
    1.类加载时机一个类型(接口/类)从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将历加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、准备、解析三个部分统称为连接(Linking)。加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程......
  • JavaWeb基础-学习笔记02
    02MySQL数据库、SQL、数据库设计、事务、JDBC1.MySQL数据库1.1MySQL的安装:包含两种安装方式,解压包安装、压缩包安装。通过解压包安装:下载压缩包后进行解压、添加my.ini初始化配置文件、添加环境变量;添加mysqld服务、启动该服务。期间遇到的一些问题和解决方案:找......
  • Java流程控制-基础语法及运用
    基本语法Scannerin=newScanner(System.in);通过Scanner类的next()与nextLine()方法获取输入的字符串,再读取前还可以进行判断是否输入了数据。next()1.一定要读取到有效字符后才可以结束输入2.对输入有效字符之前遇到的空白,next()方法会自动将其去掉3.只有输入有效字符后才可以将其......