Java核心: 类加载器

这一节我们来学习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 loadedClass<?> 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.classResource 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 URLString 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 handlerhandler = 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 bootstrapswitch (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.mzph.cn/bicheng/25369.shtml

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

相关文章

LVGL网格布局测试

一、测试1 static lv_coord_t col_dsc[] = { 80, 80, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST };static lv_coord_t row_dsc[] = { 45, LV_GRID_TEMPLATE_LAST };lv_obj_t* page = lv_img_create(lv_scr_act());lv_obj_center(page);lv_obj_set_size(page, 800, 600);isu_set_ob…

[数据集][目标检测]攀墙攀越墙壁数据集VOC格式-701张

数据集格式&#xff1a;Pascal VOC格式(不包含分割路径的txt文件和yolo格式的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;701 标注数量(xml文件个数)&#xff1a;701 标注类别数&#xff1a;1 标注类别名称:["fq"] 每个类别标…

htb-window-2-blue-smb

nmap msf 漏洞搜索 配置 获取flag

归一化在神经网络训练中的作用

归一化是深度学习中的一个重要概念&#xff0c;特别是在神经网络的训练过程中&#xff0c;它起着至关重要的作用。本文将深入探讨归一化在神经网络训练中的意义、不同的归一化方法&#xff0c;以及通过具体例子来说明归一化的实际效果。 一、什么是归一化&#xff1f; 归一化…

36python数据分析numpy基础之setxor1d求两个数组的对称差

1 python数据分析numpy基础之setxor1d求两个数组的对称差 python的numpy库的setxor1d(x,y)函数&#xff0c;表示数组x与y的对称差&#xff0c;即只属于其中一个集合&#xff0c;而不属于另一个集合的元素组成的数组&#xff0c;且进行去重排序。 用法 numpy.setxor1d(ar1, a…

SpringBoot整合钉钉实现消息推送

前言 钉钉作为一款企业级通讯工具&#xff0c;具有广泛的应用场景&#xff0c;包括但不限于团队协作、任务提醒、工作汇报等。 通过Spring Boot应用程序整合钉钉实现消息推送&#xff0c;我们可以实现以下功能&#xff1a; 实时向指定用户或群组发送消息通知。自定义消息内容…

基于关键词自动采集抖音视频排名及互动数据(点赞、评论、收藏)

在当今的社交媒体时代&#xff0c;抖音作为一个热门短视频平台&#xff0c;吸引了大量用户和内容创作者。对于研究和分析抖音上的热门视频及其互动数据&#xff08;如点赞、评论、收藏等&#xff09;&#xff0c;自动化的数据采集工具显得尤为重要。本项目旨在开发一个基于关键…

【架构分析】GPU执行GEMM矩阵运算实例演示

背景介绍 Cutlass是 NVIDIA 提供的一套用于高效实现矩阵乘法和卷积操作的 C 库。它以 CUDA 为基础&#xff0c;提供了高度优化的数学运算&#xff0c;尤其适用于GPU上的高性能并行计算。本文以GEMM矩阵运算作为实例&#xff0c;展示Cutlass在GPU上执行GEMM运算的过程 实例演示…

chatgpt 推荐的一些关于提高认知的书,我先存一下

当然可以&#xff01;以下是一些推荐的书籍&#xff0c;这些书籍涵盖了心理学、认知科学、哲学和个人发展等领域&#xff0c;可以帮助你提升认知能力和批判性思维&#xff1a; 心理学与认知科学 《Thinking, Fast and Slow》 by Daniel Kahneman 这本书探讨了人类思维的两种系…

嵌入式仪器模块:波形发生器模块(嵌入式)

• 16 位分辨率 • 125 MHz 刷新率 • 支持生成 FSK/ASK 信号 应用场景 • 生成任意标准波形或用户自定义波形 • 在特殊协议通信中模拟某个波形 • 无线充电&#xff08;信号调制&#xff09; 道114输出阻抗Low-ZLow-ZLow-Z输出范围 5 V 5 V 6 V耦合DCDCDC带宽4 MHz10 M…

将小爱音箱接入 ChatGPT 和豆包,改造成你的专属语音助手

网址 https://github.com/idootop/mi-gpt 一个ts的项目&#xff0c;看样子是个纯前端的项目。 演示的挺有意思的&#xff0c;傻妞应该是魔幻手机的角色。感觉能用这个例子的&#xff0c;最少得三十而立了。 个人感觉这种项目都是整活加炫技&#xff0c;估计我要用上这东西&…

【UML用户指南】-12-对高级结构建模-接口、类型和角色

目录 1、名称 2、操作 3、关系 4、理解接口 5、常用建模技术 5.1、对系统中的接缝建模 5.2、对静态类型和动态类型建模 5.2.1、对静态类型建模 5.2.2、对动态类型建模 使接口易于理解和易于访问 接口在关于一个抽象做什么的描述与关于这个抽象如何做的实现之间定义了…

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…

Vue如何引入ElementUI并使用

Element UI详细介绍 Element UI是一个基于Vue 2.0的桌面端组件库&#xff0c;旨在构建简洁、快速的用户界面。由饿了么前端团队开发&#xff0c;提供丰富的组件和工具&#xff0c;帮助开发者快速构建高质量的Vue应用&#xff0c;并且以开放源代码的形式提供。 1. VueElementU…

度小满金融大模型的应用创新

XuanYuan/README.md at main Duxiaoman-DI/XuanYuan GitHub

如何自动化地评估 AIGC 生图的质量?

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…

网络资源模板--基于Android Studio 实现的音乐播放器

一、项目源码获取(非开源) 关注公众号&#xff1a;《编程乐学》 后台回复&#xff1a;24060801 二、项目测试视频 网络资源模板--基于Android Studio 音乐播放器 三、项目简介 四、项目测试环境 五、项目详情设计图 1.登录注册页面介绍 <?xml version"1.0" enco…

【Git】详解本地仓库的创建、配置以及工作区、暂存区、版本库的认识

一、创建本地仓库 需要将本地仓库放在一个目录下&#xff0c;所以在创建本地仓库之前&#xff0c;应该先创建一个目录&#xff0c;再进入这个目录&#xff1a; 在这个目录中创建一个本地仓库&#xff1a; git init 创建完成后&#xff0c;我们就会发现当前目录下多了一个.git…

ssm604基于Java Web的怀旧唱片售卖系统+vue【已测试】

前言&#xff1a;&#x1f469;‍&#x1f4bb; 计算机行业的同仁们&#xff0c;大家好&#xff01;作为专注于Java领域多年的开发者&#xff0c;我非常理解实践案例的重要性。以下是一些我认为有助于提升你们技能的资源&#xff1a; &#x1f469;‍&#x1f4bb; SpringBoot…

强!推荐一款开源接口自动化测试平台:AutoMeter-API !

在当今软件开发的快速迭代中&#xff0c;接口自动化测试已成为确保代码质量和服务稳定性的关键步骤。 随着微服务架构和分布式系统的广泛应用&#xff0c;对接口自动化测试平台的需求也日益增长。 今天&#xff0c;我将为大家推荐一款强大的开源接口自动化测试平台: AutoMete…