浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系

yizhihongxing

浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系

Java类加载器负责将类的字节码从磁盘上读取到JVM内存中,并为类创建JVM运行时数据结构。JVM自带三种类加载器:启动类加载器、扩展类加载器和应用程序类加载器。Java自定义类加载器可以根据特定的需求实现不同的类加载行为和策略。

Java类加载器间的层次关系

Java类加载器中有一个明确的层次关系,如下图所示:

graph LR
    BootClassLoader --> ExtClassLoader
    ExtClassLoader --> AppClassLoader
    AppClassLoader --> UserDefinedClassLoader

先介绍JVM自带的类加载器:

启动类加载器

启动类加载器是Java虚拟机内置的类加载器,它是用来加载Java的核心库,位于JRE的lib目录下,且很少发生变化。它是所有类加载器层次结构的根,由C++实现,并不是Java类。启动类加载器无法被Java程序直接引用,因此Java程序中也无法获取到它的类对象。启动类加载器使用Bootstrap Classpath,即系统类路径,加载类。

扩展类加载器

扩展类加载器是用来加载Java平台扩展中的一些类。它位于JRE的lib目录下的ext子目录中,与启动类加载器并列。扩展类加载器使用Extension Classpath,即扩展类加载路径,加载类。

应用程序类加载器

应用程序类加载器是用来加载Java应用程序的类,所以也叫作系统类加载器。它是classpath环境变量所指定的目录中加载类。应用程序类加载器是一种常见的Java类加载器,开发者可以直接使用它。

再介绍Java自定义类加载器:

用户自定义类加载器

用户自定义类加载器是开发者提供的一种自定义实现类加载机制的方式,用来满足一些特殊的需求。用户自定义类加载器需要继承ClassLoader类,并重写它的两个重要方法 findClass(String name)和 loadClass(String name, boolean resolve)。其中findClass方法用于查找和加载类,如果进行自己的方式查找和加载失败,则直接调用父类的loadClass方法进行加载。loadClass方法用于加载类,负责在类还未被加载时,首先调用父类的loadClass方法进行加载。

Java类加载器间的协作

Java类加载器间的交互是通过父子关系实现的,每个类加载器在加载类时,先委托其父类加载器寻找该类,其它时候父类加载器不会干涉子类加载器的加载过程。当一个类加载请求到达某个类加载器时,该类加载器会先判断该类是否已经被加载过,如果没有被加载过,则该类加载器会委派其父类加载器来完成该任务。如果父类加载器不能完成,则当前类加载器才会尝试自己加载。因此,类加载器的协作关系是一种树状结构,组成了Java类加载器的层次结构。

下面给出两个实例说明类加载器间的交互关系:

示例1:父子类加载器优先级问题

假设我们自定义一个类加载器MyClassLoader,它继承自ClassLoader类,用于加载MyClass类。当前系统中使用的是应用程序类加载器,即MyClassLoader的父类加载器。那么,在程序执行过程中调用MyClassLoader类的loadClass(String name)方法时,该方法会调用到父类的loadClass方法,最终会由应用程序类加载器来尝试加载该类,如果应用程序类加载器不能加载成功,MyClassLoader才会自己尝试加载。

public class MyClass {
    static {
        System.out.println("MyClass is loaded by " + MyClass.class.getClassLoader());
    }
}
public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (!"MyClass".equals(name)) {
            // 如果不是MyClass类,则使用系统类加载器进行加载
            return super.loadClass(name);
        }

        String fileName = name + ".class";
        InputStream is = this.getClass().getResourceAsStream(fileName);
        if (is == null) {
            // 若找不到该类文件,先让系统类加载器尝试加载
            return super.loadClass(name);
        }

        try {
            byte[] bytes = new byte[is.available()];
            is.read(bytes);
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        MyClassLoader loader = new MyClassLoader();
        // 使用MyClassLoader加载MyClass类
        Class<?> clazz = loader.loadClass("MyClass");
        Object instance = clazz.newInstance();
        System.out.println(instance.getClass().getClassLoader());
    }
}

运行结果:

MyClass is loaded by MyClassLoader@3fee733d
MyClassLoader@3fee733d

可以看到,MyClassLoader成功地加载了MyClass类,并打印了加载该类的类加载器。另外,由于MyClassLoader重写了loadClass方法,并在该方法中调用了父类ClassLoader的loadClass方法,因此程序先是通过MyClassLoader来尝试加载MyClass类,由于父类查找失败,于是再次调用MyClassLoader的findClass方法来加载MyClass类。

示例2:JVM自带的类加载器

JVM内置了三种类加载器:启动类加载器、扩展类加载器、应用程序类加载器。其中启动类加载器是JVM实现的一部分,它负责加载Java核心类库。扩展类加载器和应用程序类加载器都是Java类,因此用户可以根据需要自定义这两个类加载器的行为和策略。

public class Main {
    public static void main(String[] args) {
        // 查看各种类加载器加载的类的情况
        ClassLoader cl = Main.class.getClassLoader();
        System.out.println("Main类加载器: " + cl.toString());
        System.out.println("Main的父类加载器: " + cl.getParent().toString());
        System.out.println("引导类加载器: " + cl.getParent().getParent().toString());
        System.out.println("--------------------");

        cl = int.class.getClassLoader();
        System.out.println("int类加载器: " + cl.toString());
        System.out.println("int的父类加载器: " + cl.getParent().toString());
        System.out.println("引导类加载器: " + cl.getParent().getParent().toString());
        System.out.println("--------------------");

        cl = String.class.getClassLoader();
        System.out.println("String类加载器: " + cl.toString());
        System.out.println("String的父类加载器: " + cl.getParent().toString());
        System.out.println("引导类加载器: " + cl.getParent().getParent().toString());
    }
}

运行结果:

Main类加载器: jdk.internal.loader.ClassLoaders$AppClassLoader@58372a00
Main的父类加载器: jdk.internal.loader.ClassLoaders$PlatformClassLoader@5fd0d5ae
引导类加载器: null
--------------------
int类加载器: null
java.lang.NullPointerException
    String的父类加载器: null
java.lang.NullPointerException
    引导类加载器: null
--------------------
String类加载器: null
java.lang.NullPointerException
    String的父类加载器: null
java.lang.NullPointerException
    引导类加载器: null

可以看到,基本数据类型(如int)没有类加载器,而String类型是由引导类加载器加载的,也就是说,String类是由JVM实现的一部分,不是由Java代码写出来的。

总结

Java类加载器实现了JVM的基础功能,根据不同的需求可以使用JVM自带的类加载器或自定义类加载器。每个类加载器在加载类时,都会先委托其父类加载器完成,其它时候通过自定义的方式实现类的加载和查找。熟悉Java类加载器间的协作关系和实现方式,有助于编写出更灵活的Java应用程序。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • MySQL学习笔记5:修改表(alter table)

    下面是MySQL学习笔记5的完整攻略,主要讲解如何使用ALTER TABLE命令修改表。 修改表(alter table) 1. 增加列 ALTER TABLE命令可以添加一个新列到现有表中。可以使用以下语法: ALTER TABLE table_name ADD column_name column_definition; 其中,table_name是要修…

    other 2023年6月25日
    00
  • Java 精炼解读数据结构的顺序表如何操作

    Java精炼解读数据结构的顺序表如何操作攻略 什么是顺序表 顺序表是一种基本的数据结构,它是利用一组地址连续的存储单元依次存储数据元素的线性结构。 在Java中,可以使用数组来实现顺序表。顺序表由两个主要属性组成:数组和长度。其中,数组存储了顺序表中的数据元素,长度表示当前顺序表中的元素个数。 顺序表的基本操作 初始化顺序表 在Java中,顺序表的初始化实际…

    other 2023年6月27日
    00
  • 很详细的Log4j配置步骤

    下面是“很详细的Log4j配置步骤”的完整攻略。 Log4j配置步骤 1. 引入依赖 首先,需要在项目中引入Log4j的依赖。以Maven为例,在pom.xml文件中添加以下代码: <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId…

    other 2023年6月25日
    00
  • Win11 Dev预览版25188发布:将Windows Terminal为系统默认终端

    下面我就来详细讲解“Win11 Dev预览版25188发布:将Windows Terminal为系统默认终端”的完整攻略。 背景介绍 最近,微软官方发布了Win11 Dev预览版25188,在这一版本中,微软将Windows Terminal作为系统默认终端,这是对Windows系统的一次重大改进,也意味着Windows的命令行体验将有所提升,因此很多Win…

    other 2023年6月26日
    00
  • 联想Y50用U盘改装win7的详细教程

    联想Y50用U盘改装win7的详细教程 想要更改电脑的操作系统,一般需要安装新的操作系统。在实际操作过程中,常常需要使用U盘安装,以方便快捷。本篇教程将介绍如何将联想Y50笔记本电脑用U盘改装win7。 材料准备 U盘 备份联想Y50笔记本电脑原来的操作系统备份(可选) Windows 7系统安装盘或镜像文件 联想Y50笔记本电脑 步骤一:准备U盘 将U盘插…

    其他 2023年3月28日
    00
  • C++11中std::move、std::forward、左右值引用、移动构造函数的测试问题

    C++11中move、forward、左右值引用、移动构造函数的测试问题 在 C++11 以前,当对象传递给函数时会发生对象的拷贝和移动,对于大对象的操作会对性能造成很大的影响。在 C++11 中,引入了右值引用和 move 语义,使得对象的复制和移动均可以通过引用来进行操作,避免了额外的拷贝操作,提高了程序的性能。而 std::forward 语义则是为了…

    other 2023年6月26日
    00
  • 浅析iOS给图片加水印的方法

    以下是“浅析iOS给图片加水印的方法”的详细攻略: 目录 前言 使用Core Graphics给图片加水印 示例代码 使用第三方库给图片加水印 示例代码 总结 前言 在iOS开发中,经常需要对图片进行加水印的操作。加水印可以有效地保护图片的版权,也可以用来标记图片的来源或者内容。本文将探讨两种常见的图片加水印方法:使用Core Graphics和使用第三方库…

    other 2023年6月26日
    00
  • i7 8809G/8705G值得买吗?Intel AMD合体CPU i7-8705G/8809G对比深度评测

    当然,我可以为您提供一份关于i7-8705G和i7-8809G的对比深度评测攻略。以下是完整的攻略,包含两个示例说明: i7-8705G/8809G对比深度评测 1. 性能对比 示例说明一:CPU性能 i7-8705G和i7-8809G都是Intel和AMD合作推出的混合CPU,具备强大的计算能力。然而,i7-8809G采用了更高的基础频率和更大的缓存,因此…

    other 2023年10月17日
    00
合作推广
合作推广
分享本页
返回顶部