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,一经查实,立即删除!

相关文章

[数据集][目标检测]攀墙攀越墙壁数据集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

SpringBoot整合钉钉实现消息推送

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

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

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

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

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

【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…

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

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…

手机自动化测试:4.通过appium inspector 获取相关app的信息,以某团为例,点击,搜索,获取数据等。

0.使用inspector时&#xff0c;一定要把不相关的如weditor啥的退出去&#xff0c;否则&#xff0c;净是事。 1.从0开始的数据获取 第一个位置&#xff0c;有时0.0.0.0&#xff0c;不可以的话&#xff0c;你就用这个。 第二个位置&#xff0c;抄上。 直接点击第三个启动。不要…

QA测试开发工程师面试题满分问答26: Cookie、Session、Token和JWT的定义、区别和使用场景

这是一个非常常见的面试题,需要全面掌握 Cookie、Session、Token 和 JWT 的定义和使用场景,以及它们之间的区别。下面是一个详细的满分回答: Cookie: 定义: Cookie 是一种存储在客户端(通常是浏览器)的小型文本文件,用于在客户端与服务器之间保持会话状态。使用场景: 常用于保存…

Java Web学习笔记29——Vue路由

Vue路由&#xff1a; 前端路由&#xff1a;点击菜单栏&#xff0c;地址栏会发生变化&#xff0c;会显示对应的组件。 URL中的Hash&#xff08;#号后面的部分&#xff09;与组件之间的对应关系。 Hash是/dept&#xff0c;那么就是部门管理组件&#xff1b; Hash是/emp, 那么…

Macbook M芯片Maven的安装与配置

Macbook M芯片Maven的安装与配置 下载 搜索Maven 进入网站 https://maven.apache.org/download.cgi 点击Download 点击如下链接进行下载&#xff1b; 将下载好的文件放到你的指定位置 双击进行解压 配置环境变量 进入终端 在终端中输入 open ~/.bash_profile输入以下内…

Zynq7000 系列FPGA模块化仪器

• 基于 XilinxXC7Z020 / 010 / 007S • 灵活的模块组合 • 易于嵌入的紧凑型外观结构 • 高性能的 ARM Cortex 处理器 • 成熟的 FPGA 可编程逻辑 &#xff0c;基于 IP 核的软件库 FPGA 控制器 Zynq7000 系列模块是基于 Xilinx XC7Z020/010/007S 全可编程片上系统 (SoC) 的…

湖南(品牌控价)源点调研 手机价格管理对品牌的影响分析

前言&#xff1a;手机自发明以来&#xff0c;过去一直是国际品牌占主导地位&#xff0c;从最初的爱立信、摩托罗拉&#xff0c;到后来的诺基亚、三星&#xff0c;苹果在这个手机行业里&#xff0c;竞争激励&#xff0c;没有百年企业&#xff0c;每个品牌的盛衰都有背后的历史背…

手写kNN算法的实现-用余弦相似度来度量距离

设a为预测点&#xff0c;b为其中一个样本点&#xff0c;在向量空间里&#xff0c;它们的形成的夹角为θ&#xff0c;那么θ越小&#xff08;cosθ的值越接近1&#xff09;&#xff0c;就说明a点越接近b点。所以我们可以通过考察余弦相似度来预测a点的类型。 from collections i…