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

浅谈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日

相关文章

  • JS实现表单多文件上传样式美化支持选中文件后删除相关项

    JS实现表单多文件上传样式美化支持选中文件后删除相关项的完整攻略主要包括以下三个步骤: HTML结构的搭建 在HTML结构中,我们需要使用文件输入框和上传按钮,同时利用CSS样式美化显示效果。具体HTML结构如下: <div id="upload-area"> <input type="file" i…

    other 2023年6月27日
    00
  • 查看crontab任务执行情况

    以下是查看crontab任务执行情况的完整攻略: 1. 查看cron日志 cron是一个系统级的定时任务管理器,可以在定的时间间隔内运行命令或脚本。任务的执行情况可以在系统日志中查看。可以使用以下命令查看cron日志: sudo grep CRON /var/log/syslog 该命令将显示所有cron任务的执行情况,包括任务的执行时间和执行结果。 2. …

    other 2023年5月8日
    00
  • c语言 字符串的拼接和分割实例

    C语言字符串的拼接 在C语言中,可以使用strcat()函数将两个字符串进行拼接。它的原型如下: char *strcat(char *dest, const char *src); 其中,dest表示目标字符串,会在其后添加src所指向的字符串;src表示要添加到目标字符串dest中的源字符串,它不会被修改。 下面是一个示例代码,演示如何使用strcat(…

    other 2023年6月20日
    00
  • 详解vue route介绍、基本使用、嵌套路由

    Vue Router 详解 Vue Router 是 Vue.js 官方的路由管理器,它可以帮助我们在 Vue 应用中实现页面之间的导航和路由功能。本文将详细介绍 Vue Router 的基本使用和嵌套路由,并提供两个示例说明。 基本使用 首先,我们需要安装 Vue Router。可以通过 npm 或 yarn 进行安装: npm install vue-r…

    other 2023年7月28日
    00
  • 检测jQuery.js是否已加载的判断代码

    为了检测jQuery.js是否已经加载,我们可以利用一些JavaScript代码来实现。 使用typeof判断 通过typeof可以检测一个变量或者对象的类型,如果对象没有被定义,那么其类型就是undefined。我们利用这个特性来判断jQuery是否已经被加载。 if (typeof jQuery == "undefined") { /…

    other 2023年6月25日
    00
  • web前端助手(fehelper)

    Web前端助手(fehelper)完整攻略 Web前端助手(fehelper)是一款Chrome浏览器插件,它提供了一系列实用前端开发具,包括页面元素查看、CSS样式查看、JS调试、JSON格式化、二维码生成等功能。本攻略将详细绍Web前端助手的安装、配置和使用方法,包括基本概念、安装配置和示例说明。 基本概念 Web前端助手(fehelper)是一款Chr…

    other 2023年5月6日
    00
  • C语言的数组与指针可以这样了解

    C语言中的数组和指针都是非常重要的概念,它们在编程中广泛应用。本篇攻略将阐述数组和指针的基本概念、如何使用数组和指针以及它们之间的关系。 1. 数组 1.1 基本概念 数组是一组具有相同数据类型的变量组成的有序集合。数组的每个元素可以通过下标来访问,下标从0开始,最大值为数组长度减1。 定义一个数组的方法如下: int arr[10]; 上述语句定义了一个大…

    other 2023年6月25日
    00
  • Android 生命周期架构组件使用方法

    Android 生命周期架构组件使用方法 Android 生命周期架构组件是为了帮助程序员更方便地管理应用的生命周期而设计的。在本文中,我们将详细讲解 Android 生命周期架构组件的使用方法。 组件介绍 Android 生命周期架构组件包括以下几个组件: ViewModel:负责管理 UI 周期内需要保留的数据。 LiveData:用于展示数据变化,并帮…

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