Java_类加载器

news/2025/2/6 0:43:54 标签: java, 开发语言, 类加载器

小程一言

本专栏是对Java知识点的总结。在学习Java的过程中,学习的笔记,加入自己的思考,结合各种资料的整理。

文章与程序一样,一定都是不完美的,因为不完美,才拥有不断追求完美的动力

类加载器的基础

首先要明确类加载器的职责就是加载类。当JVM启动时,系统会使用类加载器将必要的类(如java.lang.*类库)加载到内存中。Java中的类加载器是递归结构,遵循“双亲委派模型”。

双亲委派模型

核心思想

当一个类加载器接到加载类的请求时,它不会立即去加载该类,而是先将请求委派给它的父类加载器。只有当父类加载器无法完成加载时,子类加载器才会尝试自己加载。

优势

它保证了Java的核心类库(如java.lang.String)始终由系统的启动类加载器来加载,避免了“重复加载”问题,并增加了类加载的安全性。

双亲委派模型的加载顺序:

  1. 启动类加载器
  2. 扩展类加载器
  3. 系统类加载器
  4. 自定义类加载器
    在这里插入图片描述

类加载器的职责

  • 启动类加载器(Bootstrap ClassLoader):负责加载JVM的核心类库,如java.lang.*包中的类。它是Java中最底层的类加载器,通常由C或C++编写,并且由JVM本身提供。

  • 扩展类加载器(Extension ClassLoader):负责加载jre/lib/ext目录下的类或JVM扩展目录中的类。它的父类是启动类加载器

  • 系统类加载器(System ClassLoader):负责加载应用程序的类路径(Classpath)中指定的类。它通常是由用户定义的类所在的目录、JAR包等。

  • 自定义类加载器(Custom ClassLoader):由开发者自定义的类加载器,可以扩展现有的类加载器,来加载类文件、网络上的类、数据库中的类等。

类加载器的工作流程

类加载器的工作流程可以简单分为以下几个步骤:

  1. 加载类加载器根据类名,查找并读取相应的字节码文件(.class文件)。
  2. 验证:JVM会验证字节码文件的有效性,确保它符合Java的语言规范。
  3. 准备:为类的静态字段分配内存,并为它们赋默认值(如null0等)。
  4. 解析:将类中的符号引用转换为直接引用,主要是指将常量池中的符号,如类名、字段名、方法名等,解析为内存地址。
  5. 初始化:执行类的静态初始化代码(如静态变量的初始化和静态块的执行)。
    在这里插入图片描述

举例:如何在Java中使用类加载器

启动类加载器、扩展类加载器与系统类加载器

java">public class ClassLoaderExample {
    public static void main(String[] args) {
        // 获取当前类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系统类加载器:" + systemClassLoader);

        // 获取扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println("扩展类加载器:" + extClassLoader);

        // 获取启动类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println("启动类加载器:" + bootstrapClassLoader);
    }
}

输出

系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
扩展类加载器:sun.misc.Launcher$ExtClassLoader@5e91993f
启动类加载器:null

解释

自定义类加载器

通过继承ClassLoader类来实现自定义类加载器。假设我们有一个自定义的MyClassLoader,它从特定的目录加载类:

java">import java.io.*;
public class MyClassLoader extends ClassLoader {
    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = classPath + name.replace('.', File.separatorChar) + ".class";
        File file = new File(path);
        if (file.exists()) {
            try (FileInputStream inputStream = new FileInputStream(file)) {
                byte[] bytes = new byte[(int) file.length()];
                inputStream.read(bytes);
                return defineClass(name, bytes, 0, bytes.length);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("path/to/classes/");

        // 使用自定义类加载器加载类
        Class<?> clazz = myClassLoader.loadClass("com.example.MyClass");

        System.out.println("加载的类: " + clazz.getName());
    }
}

本例中,定义了一个MyClassLoader,它从指定的路径加载类文件。当你调用loadClass()时,它会尝试使用findClass()方法加载类。如果找不到,系统类加载器会被调用。

例如,在使用自定义类加载器加载完类后,如果不再需要这些类,类加载器本身也会被垃圾回收,类将被卸载。类卸载通常发生在类加载器不再有任何引用,并且类加载器本身被回收时。
在这里插入图片描述

类加载器与类冲突

有时会遇到不同的类加载器加载同一个类的情况。如下:

java">public class TestClass {
    public void printMessage() {
        System.out.println("Hello from TestClass!");
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader1 = new MyClassLoader("path/to/classes/");
        MyClassLoader myClassLoader2 = new MyClassLoader("path/to/classes/");

        Class<?> clazz1 = myClassLoader1.loadClass("TestClass");
        Class<?> clazz2 = myClassLoader2.loadClass("TestClass");

        System.out.println("clazz1 == clazz2: " + (clazz1 == clazz2));
    }
}

在这个例子中,我们创建了两个不同的MyClassLoader,并用它们分别加载TestClass类。由于每个MyClassLoader加载的是不同的TestClass实例,clazz1 == clazz2将会输出false,表明它们是不同的类。
在这里插入图片描述

总结

  • Java的类加载器机制确保了类的加载、验证、链接和初始化的顺序。
  • 类加载器采用双亲委派模型,从而保证了系统类库的统一加载,避免了类的重复加载。
  • 你可以通过继承ClassLoader类来自定义类加载器,控制类的加载过程。
  • 类加载器的生命周期与JVM的生命周期相匹配,因此类会在合适的时候被卸载,防止内存泄漏。

http://www.niftyadmin.cn/n/5842494.html

相关文章

nginx 新手指南

文章来源&#xff1a;https://nginx.cadn.net.cn/beginners_guide.html 本指南对 nginx 进行了基本的介绍&#xff0c;并描述了一些 可以用它完成的简单任务。 假设 nginx 已经安装在阅读器的机器上。 如果不是&#xff0c;请参阅 安装 nginx 页面。 本指南介绍如何启动和停止…

【物联网】ARM核常用指令(详解):数据传送、计算、位运算、比较、跳转、内存访问、CPSR/SPSR

文章目录 指令格式&#xff08;重点&#xff09;1. 立即数2. 寄存器位移 一、数据传送指令1. MOV指令2. MVN指令3. LDR指令 二、数据计算指令1. ADD指令1. SUB指令1. MUL指令 三、位运算指令1. AND指令2. ORR指令3. EOR指令4. BIC指令 四、比较指令五、跳转指令1. B/BL指令2. l…

5. k8s二进制集群之ETCD集群部署

下载etcd安装包创建etcd配置文件准备证书文件和etcd存储目录ETCD证书文件安装(分别对应指定节点)创建证书服务的配置文件启动etcd集群验证etcd集群状态继续上一篇文章《k8s二进制集群之ETCD集群证书生成》下面介绍一下etcd证书生成配置。 下载etcd安装包 https://github.com…

基于PyQt5打造的实用工具——PDF文件加图片水印,可调大小位置,可批量处理!

01 项目简介 &#xff08;1&#xff09;项目背景 随着PDF文件在信息交流中的广泛应用&#xff0c;用户对图片水印的添加提出了更高要求&#xff0c;既要美观&#xff0c;又需高效处理批量文件。现有工具难以实现精确调整和快速批量操作&#xff0c;操作繁琐且效果不理想。本项…

如可安装部署haproxy+keeyalived高可用集群

第一步&#xff0c;环境准备 服务 IP 描述 Keepalived vip Haproxy 负载均衡 主服务器 Rip&#xff1a;192..168.244.101 Vip&#xff1a;192.168.244.100 Keepalive主节点 Keepalive作为高可用 Haproxy作为4 或7层负载均衡 Keepalived vip Haproxy 负载均衡 备用服务…

Leetcode Hot100 61-65

法一&#xff1a;使用辅助栈 一个栈正常存&#xff0c;另一个存最小值 class MinStack {Deque<Integer>stack;Deque<Integer>minstack;public MinStack() {stack new LinkedList<>();minstack new LinkedList<>();}public void push(int val) {stac…

洛谷P3884 [JLOI2009] 二叉树问题(详解)c++

题目链接&#xff1a;P3884 [JLOI2009] 二叉树问题 - 洛谷 | 计算机科学教育新生态 1.题目解析 1&#xff1a;从8走向6的最短路径&#xff0c;向根节点就是向上走&#xff0c;从8到1会经过三条边&#xff0c;向叶节点就是向下走&#xff0c;从1走到6需要经过两条边&#xff0c…

kubernetes学习-配置管理(九)

一、ConfigMap &#xff08;1&#xff09;通过指定目录&#xff0c;创建configmap # 创建一个config目录 [rootk8s-master k8s]# mkdir config[rootk8s-master k8s]# cd config/ [rootk8s-master config]# mkdir test [rootk8s-master config]# cd test [rootk8s-master test…