Java核心: 类加载器

news2024/12/24 3:26:04

这一节我们来学习Java的类加载器,以及常用的类加载器实现URLClassLoader。

1. Java类加载器

类加载器用于将字节码读取并创建Class对象。我们知道JVM本身是用C写的,一开始执行的时候由C程序来加载并引导字节码的运行,这些由C编写的加载字节码的类加载器被称为BootstrapClassLoader。BootstrapClassLoader负责加载Java的运行时(${JAVA_HOME}/jre/lib),比如rt.jar、resources.jar。 Java运行时实现了自己的类加载器PlatformClassLoader,负责加载扩展类库(${JAVA_HOME}/jre/lib/ext)。 此外Java运行时还提供了AppClassLoader用于加载CLASSPATH下指定的jar。

类加载器

说明

加载的范围

BootstrapClassLoader

根类加载器,加载JRE核心,C/C++实现

JAVA_HOME/jre/lib

PlatformClassLoader

平台类加载器,加载JRE的扩展类

JAVA_HOME/jre/lib/ext

AppClassLoader

应用类加载器,加载应用类

ClassPath的目录或jar文件

1. 加载过程

类的加载过程被拆分为3步: 装载、链接、初始化,链接本身由被拆为三步: 验证、准备、解析,我们来看看每一步,具体完成了什么操作

  1. 装载,读取字节码(.class文件),创建Class对象
  2. 链接
    1. 验证,校验类文件结构,检查字节码是否合法,是否符合访问权限
    2. 准备,为类静态变量分配空间,设置默认初始值
    3. 解析,将符号引用解析为直接引用,包括类、方法、字段等,指向内存地址
  3. 初始化
    1. 字段的静态初始化,赋予初始化值
    2. 静态初始化块执行

访问类的静态方法和字段、通过反射调用类、创建类的实例都会触发类的初始化,子类被初始化时也会触发父类的初始化。不过有一些特殊的场景,不会执行类的初始化,包括:

  1. 访问静态常量(static final),这类常量被编译优化内联到代码里,因此不需要初始化
  2. 通过子类引用父类的静态字段,不会触发子类的静态初始化
  3. 定义类的数组引用但不赋值,不会触发类的初始化
  4. ClassLoader.load传入参数resolve=false,不会触发链接和初始化
2. 双亲委派

如果我在ClassPath里放一个java.lang.String的类实现,用它替换Java的内置实现的话,风险还是很大的,毕竟用户输入的账号密码都会表示为String对象。双亲委派策略就是用来避免这个问题的。

所谓的双亲委派是指我们用AppClassLoader装载一个类时,会先委托父加载器来加载, 父加载器又会委托它的父加载器加载,只有父加载器无法完成加载的时候,才会尝试自己加载。当加载String类时,会先委托给BootstrapClassLoader,即使用JAVA_HOME/jre/lib中的实现,即使在ClassPath中有java.lang.String的实现,也没有机会被加载。

从代码实现上看也很简单,加载类时先调用父加载器的方法(parent.loadClassOrNull),只有父加载器找不到时,才从自己的ClassPath上查找。详见Java17内置代码: BuiltinClassLoader.loadClass

protected Class<?> loadClassOrNull(String cn, boolean resolve) {

    synchronized (getClassLoadingLock(cn)) {                              // 加锁,避免同一个类被并发加载
        // check if already loaded
        Class<?> c = findLoadedClass(cn);                                 // 如果已加载,直接返回已加载的Class对象

        if (c == null) {
            ...
            if (parent != null) {                                         // 如果有父加载器
                c = parent.loadClassOrNull(cn);                           // 尝试用父加载器加载
            }
            ...
            if (c == null && hasClassPath() && VM.isModuleSystemInited()) {
                c = findClassOnClassPathOrNull(cn);                       // 从ClassPath查找
            }
        }

        if (resolve && c != null)
            resolveClass(c);                                              // 链接

        return c;
    }
}


private Class<?> findClassOnClassPathOrNull(String cn) {
    String path = cn.replace('.', '/').concat(".class");                  // com.keyniu.Main -> com/keyniu/Main.class
    Resource res = ucp.getResource(path, false);                          // 使用URLClassPath读取.class文件
    if (res != null) {
        try {
            return defineClass(cn, res);                                  // 读取Resource装载成Class对象
        } catch (IOException ioe) {
            // TBD on how I/O errors should be propagated
        }
    }
    return null;
}
3. URLClassPath

BultinClassLoader.findClassOnClassPathOrNull(String cn)实际是通过URLClassPath.getResource读取.class文件的。URLClassPath相关的核心类如下图所示

  1. URLClassPath,内部封装了查找路径(比如我们常说的ClassPath),URLStreamHandler用于支持不同的Schema读取(如file://、jar://),Loader封装了资源查找的逻辑
  2. URLStreamHandler,支持自定义不同Schame的资源读取,比如file://、jar://
  3. URLStreamHandlerFactory,抽象工厂模块,能整套的替换URLStreamHandler实现
  4. Loader/JarLoader,负责指定Schema指定文件夹/jar文件下读取指定资源

如果我们想要通过URLClassPath加载资源,主要分为3个用例,分别是创建URLClassPath、查找资源、读取资源

  1. 创建URLClassPath,通过构造函数、addFile、addURL将要搜索的根路径传递给URLClassPath
  2. 查找资源findResource(String name, boolean check)方法提供支持,在第1步设置的根路径基础上搜索
    1. 根据根路径的差异,选择Loader、JarLoader、FileLoader等实现
    2. 调用Loader.findResource,查找根路径下是否有对于文件
    3. 返回第一个非空Resource对象,内部包装了URL对象
  3. 由ClassLoader读取第2个用例返回的Resource对象,调用defineClass创建Class对象

我们贴一下整个流程中的核心代码(Java17),为方便阅读由做删减,只保留我们关心的流程。首先看的是URLClassPath根据URL获取Loader实例的逻辑,根据URL的协议和路径生成

  1. 如果是文件(路径不以"/"结尾),直接返回JarLoader
  2. 否则,根据协议返回FileLoader、JarLoader,都不是的话返回Loader
// URLClassPath.getLoader
private Loader getLoader(final URL url) throws IOException {
    String protocol = url.getProtocol();  // lower cased in URL
    String file = url.getFile();
    if (file != null && file.endsWith("/")) {
        if ("file".equals(protocol)) {
            return new FileLoader(url);
        } else if ("jar".equals(protocol) && isDefaultJarHandler(url) && file.endsWith("!/")) {
            URL nestedUrl = new URL(file.substring(0, file.length() - 2));
            return new JarLoader(nestedUrl, jarHandler, lmap, acc);
        } else {
            return new Loader(url);
        }
    } else {
        return new JarLoader(url, jarHandler, lmap, acc);
    }
}

使用URL.openConnection时会使用URLStreamHandler,而默认URLStreamHandler是通过DefaultFactory.createStreamHanlder实现的。

// URL.openConnection
public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}

// URL.getURLStreamHandler
static URLStreamHandler getURLStreamHandler(String protocol) {
    ...
    if (handler == null) {
        // Try the built-in protocol handler
        handler = defaultFactory.createURLStreamHandler(protocol);
    }
    ...
    return handler;
}

// DefaultFactory.createURLStreamHandler
private static class DefaultFactory implements URLStreamHandlerFactory {
    private static String PREFIX = "sun.net.www.protocol.";
    public URLStreamHandler createURLStreamHandler(String protocol) {
        // Avoid using reflection during bootstrap
        switch (protocol) {
            case "file":
                return new sun.net.www.protocol.file.Handler();
            case "jar":
                return new sun.net.www.protocol.jar.Handler();
            case "jrt":
                return new sun.net.www.protocol.jrt.Handler();
        }
        String name = PREFIX + protocol + ".Handler";
        Object o = Class.forName(name).getDeclaredConstructor().newInstance();
        return (URLStreamHandler)o;
    }
}

2. URLClassLoader

讲了这么多,我们来看看一个实例,Java内置的ClassLoader实现: URLClassLoader。URLClassLoader的核心功能是两个

  1. findClass(final String name),加载一个类
  2. findResource(String name),读取一个资源文件
1. 源码阅读

加载资源和类的实现,从代码上看还是很简单,直接调用URLClassPath ucp来加载资源,如果是类加载的话读取资源并调用defineClass创建类

public URL findResource(final String name) {
    return ucp.findResource(name, true);
}


protected Class<?> findClass(final String name) throws ClassNotFoundException {
    String path = name.replace('.', '/').concat(".class");
    Resource res = ucp.getResource(path, false);

    return defineClass(name, res);

}
2. 使用示例

我们用URLClassLoader来加载一个SpringBoot应用的jar来做个测试,这个jar解压后的结构看起来是这样的

我们来看一下URLClassLoader读取这个jar的代码

private static void testURLClassLoader(URL url) throws ClassNotFoundException {
    URLClassLoader ucl = new URLClassLoader("MyURLClassLoader", new URL[]{url}, null);
    Class<?> clazz = ucl.loadClass("org.springframework.boot.loader.JarLauncher");             // 标准jar结构中的类自动加载
    System.out.println(clazz);

    URL is = ucl.getResource("META-INF/MANIFEST.MF");                                          // META-INF下自动加载
    System.out.println(is);

    is = ucl.getResource("BOOT-INF/layers.idx");                                               // BOOT-INF下自动加载
    System.out.println(is);

    is = ucl.getResource("org/springframework/boot/loader/launch/JarLauncher.class");          // 标准jar结构的.class也能加载
    System.out.println(is);

    clazz = ucl.loadClass("com.keyniu.yangsi.YangsiApplication");                              // URLClassLoader不会加载BOOT-INF/classes、BOOT-INF/lib下的类
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1806605.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图神经网络(GNN)的原理及应用

什么是图神经网络 &#xff08;GNN&#xff09;&#xff1f; 图神经网络 &#xff08;GNN&#xff09; 是一种神经网络架构和深度学习方法&#xff0c;可以帮助用户分析图&#xff0c;使他们能够根据图的节点和边描述的数据进行预测。 图形表示数据点&#xff08;也称为节点&…

ENSP校园网设计实验

前言 哈喽&#xff0c;我是ICT大龙。本次更新了使用ENSP仿真软件设计校园网实验。时间比较着急&#xff0c;可能会有错误&#xff0c;欢迎大家指出。 获取本次工程文件方式在文章结束部分。 拓扑设计 拓扑介绍---A校区 如图&#xff0c;XYZ大学校园网设计分为3部分&#xff0…

硬盘坏了数据能恢复吗 硬盘数据恢复一般多少钱

在数字化时代&#xff0c;我们的生活和工作离不开电脑和硬盘。然而&#xff0c;硬盘故障是一个常见的问题&#xff0c;可能会导致我们的数据丢失。当我们的硬盘坏了&#xff0c;还能恢复丢失的数据吗&#xff1f;今天我们就一起来探讨关于硬盘坏了数据能恢复吗&#xff0c;硬盘…

请求 响应

在web的前后端分离开发过程中&#xff0c;前端发送请求给后端&#xff0c;后端接收请求&#xff0c;响应数据给前端 请求 前端发送数据进行请求 简单参数 原始方式 在原始的web程序中&#xff0c;获取请求参数&#xff0c;需要通过HttpServletRequest 对象手动获取。 代码…

如何在本地和远程删除 Git 分支

欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;目前是武汉城市开发者社区主理人 擅长.net、C、python开发&#xff0c; 如果遇…

在线按模板批量生成文本工具

具体请前往&#xff1a;在线按模板批量生成文本工具

【手推公式】如何求SDE的解(附录B)

【手推公式】如何求SDE的解&#xff08;附录B&#xff09; 核心思路&#xff1a;不直接求VE和VP的SDE的解xt&#xff0c;而是求xt的期望和方差&#xff0c;从而写出x0到xt的条件分布形式&#xff08;附录B&#xff09; 论文&#xff1a;Score-Based Generative Modeling throug…

2024年人工智能与云计算国际会议(ICAICC 2024)

2024 International Conference on Artificial Intelligence and Cloud Computing 【1】大会信息 大会时间&#xff1a;2024-07-19 大会地点&#xff1a;中国长沙 截稿时间&#xff1a;2024-07-05(以官网为准&#xff09; 审稿通知&#xff1a;投稿后2-3日内通知 会议官网&am…

Pico4 MR Unity零基础开发之开启MR透视

一、新建场景&#xff1a;SeethroughScene 1、新建场景。 二、添加 XR 摄像机进行设置 1、在 Hierarchy 窗口中&#xff0c;右击默认添加的 Main Camera&#xff0c;然后点击 Delete 将其删除。 2、点击 > XR > XR Origin (VR)&#xff0c;将 XR Origin 添加至场景 3、…

股票数据集2-纳斯达克NASDAQ 100 分析

1. 数据清洗 用邻近均值的方法&#xff0c;去掉Non_Padding中的NaN数据 这里没用df.fillna(), 因为其只有前向(ffill )和 后向 (bfill) 插值&#xff0c;不适合大量连续的NaN pd转换为np&#xff0c;写一个函数, 返回np数组的空值&#xff0c;lambda的匿名函数返回y轴空值的索…

CSAPP Lab01——Data Lab完成思路

陪你把想念的酸拥抱成温暖 陪你把彷徨写出情节来 未来多漫长再漫长还有期待 陪伴你 一直到 故事给说完 ——陪你度过漫长岁月 完整代码见&#xff1a;CSAPP/datalab-handout at main SnowLegend-star/CSAPP (github.com) 01 bitXor 这道题是用~和&计算x^y。 异或是两个…

mongodb总概

一、mongodb概述 mongodb是最流行的nosql数据库&#xff0c;由C语言编写。其功能非常丰富&#xff0c;包括: 面向集合文档的存储:适合存储Bson(json的扩展)形式的数据;格式自由&#xff0c;数据格式不固定&#xff0c;生产环境下修改结构都可以不影响程序运行;强大的查询语句…

著名AI人工智能社会学家唐兴通谈数字社会学网络社会学主要矛盾与数字空间社会网络社会的基本议题与全球海外最新热点与关注社会结构社会分工数字财富数字游民数字经济

如果人工智能解决了一切&#xff0c;人类会做什么&#xff1f; 这个问题的背后是人工智能时代的社会主要矛盾会是什么&#xff1f;那么整个社会的大的分工体系就会围绕主要矛盾开展。 《人工智能社会主要矛盾》 在农业社会&#xff0c;主要矛盾是人口增长和土地资源之间的关…

atcoder abc357

A Sanitize Hands 问题&#xff1a; 思路&#xff1a;前缀和&#xff0c;暴力&#xff0c;你想咋做就咋做 代码&#xff1a; #include <iostream>using namespace std;const int N 2e5 10;int n, m; int a[N];int main() {cin >> n >> m;for(int i 1…

【日常记录】【JS】中文转拼音的库 pinyin-pro

文章目录 1、介绍2、pinyin-pro 基本使用3、参考链接 1、介绍 pinyin-pro 是一个专业的 JavaScript 中文转拼音的库&#xff0c;具备多音字识别准确、体积轻量、性能优异、功能丰富等特点。 常用的案例 搜索功能增强&#xff1a;在输入框输入汉字时&#xff0c;可以转化为拼音输…

英特尔:AI落地,未来已来

引言 随着AI技术的发展和大模型的普及&#xff0c;人工智能正在逐渐渗透到我们的日常生活中。2023年5月底&#xff0c;我参加了台北的英特尔技术展&#xff0c;深入了解了英特尔在AI个人电脑领域的最新进展。本文将详细介绍英特尔的新一代移动处理器Lunar Lake&#xff0c;以及…

【C51】C51单片机实现的 抽奖机 设计与编程指南

文章目录 前言&#xff1a;1. 实现效果2. 准备工作3. 编写代码总结&#xff1a; 前言&#xff1a; 在本文中&#xff0c;我们将介绍如何使用C51单片机来实现一个简单的抽奖机。这个项目不仅能够展示C51单片机的基本应用&#xff0c;还能让我们了解如何通过编程来控制硬件&…

卡尔曼滤波器例子

卡尔曼滤波器 卡尔曼滤波器(Kalman Filter)是一种用于线性系统状态估计的递归算法,可以有效地融合传感器数据和系统模型来估计系统的状态。它在机器人学中广泛应用,尤其是位置和速度等状态的估计。通过卡尔曼滤波器,可以有效地估计机器人在二维平面内的真实位置,并减小测…

探地雷达正演模拟,基于时域有限差分方法,一

声明&#xff1a;本博客中的公式均是在Word中使用AxMath写好后截图使用的&#xff0c;欢迎引用&#xff0c;但请标注来源。 本系列会有四篇博客&#xff1a; 第一篇内容&#xff1a; 1、基础知识掌握 2、Maxwell方法差分求解原理 第二篇内容&#xff1a; 1、基于C的TE波波…

Nvidia/算能 +FPGA+AI大算力边缘计算盒子:大疆RoboMaster AI挑战赛

NVIDIA Jetson TX2助力机器人战队斩获RoboMaster AI挑战赛冠亚军 一个汇聚数百万机器人专家与研究人员的赛场&#xff0c;一场兼具工程、策略和团队挑战的较量&#xff0c;说的正是近日刚刚在澳大利亚布里斯本ICRA大会上闭幕的大疆RoboMaster AI挑战赛今年的冠军I Hiter以及亚军…