JVM 类的加载子系统
文章目录
- 类的加载过程
- 加载阶段
- 链接阶段
- 初始化
- 类的加载器
- 测试代码中获取对应的加载器
- 获取加载器加载的路径
- 不同类对应的加载器
- 自定义加载器
- 自定义加载器的方式
- 获取类的加载器的方式
- 双亲委派机制
- 双亲委派机制的好处
- Java 的 SPI 机制
- 1. 接口定义
- 2. 具体实现
- 3. 配置 META-INF/services 文件
- 4. 接口的使用
类的加载过程
类的加载过程分为3个阶段
- 加载阶段
- 链接阶段
- 初始化阶段
类的加载器,只负责加载 .class 文件,至于能不能执行,是执行引擎决定的。
加载阶段
- 通过类的全限定名获取此类的二进制流
- 将此类的二进制流中静态储存结构存储在运行时数据区的方法区(元空间 > 7.0 或永久代 < 7.0)
- 在内存中生成一个 java.lang.Class 的对象,作为方法区在这个类的各种数据的访问入口
虽然一般情况下,JVM 加载的是 .class 文件,其实只要是符合 JVM 的字节码都可以进行加载。比如:.jar 包中的文件,动态代理生成的字节码(可在运行时动态生成),还比如加密的 .class 文件,通过 JVM 解密后加载,可有效防止反编译。
链接阶段
- 验证(Verify)
- 每个 .class 文件都有一个特殊的开头,用以表示该文件是 JVM 支持的 .class 文件。验证的目的在于确保被加载的类的正确性。
- 主要验证方式:文件格式验证、元数据验证、字节码验证、符号引用验证
- 准备(Prepare)
- 为类变量分配内存并且设置类变量的初始值(注意:初始值为 0)。如:private static int a = 10;这个时候 a 的值为 0;特殊的:如果变量用 final 修饰,如:private final static int b = 10;这个时候 b 的值为 10;因为 final 修饰表示 b 为常量,在编译期初始化。
- 此时成员变量不会初始化,static 修饰的 a 为类变量,类变量在类加载的时候初始化,如 private int c = 10;c 是成员变量,是和对象一起被分配到 Java 堆中的。
- 解析(Resolve)
- 将常量池内的符号引用转换为直接引用的过程
- 解析主要针对类或接口、字段、各种方法(类方法、接口方法等)
初始化
- 初始化静态变量和静态块,此对应执行类构造器方法<clinit>(),此方法的指令按源文件中的顺序执行。如果没有相关静态变量或静态块,可能不会有 <clinit>() 方法。
- 成员变量和局部变量对应 JVM 下的 <init>()方法。
类的加载器
- 引导类(启动类)加载器(Bootstrap ClassLoder):负责加载 Java 的核心类库(JAVA_HOME/jre/lib/rt.jar、resource.jar、sun、java、javax 包开头的类 ),使用 C/C++ 实现的,并不继承自 ClassLoader 类。
- 自定义类加载器:直接或间接继承自 ClassLoader 类的类加载器。是由 Java 实现的。
- 扩展类加载器(sun.misc.Launcher$ExtClassLoader):ExtClassLoader 是 Launcher 对象的内部类,其父类加载器为引导类加载器,其加载的是系统属性 java.ext.dirs 所指定的目录 或者 从 JAVA_HOME/jre/lib/ext 目录下加载类。(我们自定义的 jar 包放在此目录也会被加载)
- 系统类(应用程序类)加载器(sun.misc.Launcher$AppClassLoader):其父类加载器为扩展类加载器,负责加载环境变量 classpath 或 系统属性 java.class.path 所指定的目录下的类。
- 其他自定义的类加载器
注意,他们不是继承关系,我们可以称他们为扩展关系。
测试代码中获取对应的加载器
public static void main(String[] args) {ClassLoader app = ClassLoader.getSystemClassLoader();System.out.println(app);ClassLoader ext = app.getParent();System.out.println(ext);ClassLoader bootstrap = ext.getParent();System.out.println(bootstrap);}
结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@6d03e736
null
程序中的默认类加载器为 AppClassLoader 。一般情况下,Java 应用中的类都是由 AppClassLoader 加载器加载。其通过 ClassLoader.getSystemClassLoader() 方法获取。
Java 代码中不能直接获取引导类加载器实例。所以示例中 bootstrap 为 null
获取加载器加载的路径
// 获取 Bootstrap 加载器的加载路径URL[] urls = Launcher.getBootstrapClassPath().getURLs();for(URL url : urls){System.out.println(url.getFile());}System.out.println("======================");// 获取扩展类加载器加载的的路径String dirs = System.getProperty("java.ext.dirs");System.out.println(dirs);
结果
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/resources.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/rt.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/sunrsasign.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jsse.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jce.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/charsets.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfr.jar
/C:/Program%20Files/Java/jdk1.8.0_181/jre/classes
======================
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
Bootstrap 加载器的加载路径下的 jar 包文件包含的类,由 Bootstrap 加载器加载,两个 ext 目录下的 jar 包下的类由 ExtClassLoader 加载器加强,我们的应用程序,classpath 属性下的类,由 AppClassLoader 加载。
不同类对应的加载器
ClassLoader classLoader = String.class.getClassLoader();System.out.println(classLoader);ClassLoader classLoader1 = ExecutorTest.class.getClassLoader();System.out.println(classLoader1);
结果
null
sun.misc.Launcher$AppClassLoader@18b4aac2
说明 String 类是由引导类加载器加载,其引导类加载器无法在代码中获取,所以为 null 。ExecutorTest 是我们的示例对象,其由 AppClassLoader 加载器加载。当然,我们在 ext 目录下找到的 jar 包中的类,由 ExtClassLoader 加载器加载。
自定义加载器
一般的 Java 程序中,使用引导类加载器、扩展类加载器、系统类加载器相互作用,即可。几乎不需要自定义类的加载器,我们可以在某些情况下进行自定义加载器。
- 修改类的加载方式
- 还比如我们对编译的源码进行了加密,在类加载时需要解密等
自定义加载器的方式
- 继承 ClassLoader 重写 findClass(String name) 方法
- 按照 URLClassLoader.FactoryURLClassLoader 继承 URLClassLoader 的方式继承 URLClassLoader 来实现即可。( URLClassLoader 可以加载 jar 包下的类)
package com.yyoo.jvm;import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;@Getter
@Setter
@AllArgsConstructor
public class MyClassLoader extends ClassLoader{/*** 当前加载器的 class 文件根路径*/private String classRootPath;@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 使用 io 流读取.class 字节码文件,然后使用父类的 defineClass 方法返回为 Class 类try (InputStream in = new FileInputStream(getClassRootPath()+"\\"+name.replaceAll(".","\\")+".class")){byte[] b = new byte[1024];int len = in.read(b);return defineClass(name,b,0,len);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}throw new ClassNotFoundException();}}
name 参数使用class的全类名即可。注:我们示例使用了 lombok 插件。
获取类的加载器的方式
// 获取当前类的ClassLoader
Object o = new Object();
System.out.println(o.getClass().getClassLoader());// 获取当前线程上下文的ClassLoder
System.out.println(Thread.currentThread().getContextClassLoader());// 获取当前系统的 ClassLoder
System.out.println(ClassLoader.getSystemClassLoader());
双亲委派机制
类的加载过程:
- 类的加载器收到类的加载请求时,它不会立马去加载,而且把加载请求委托给父类加载器去执行。
- 如果父类加载器还有父类加载器,则会进一步委托给父类加载器加载,直到最顶层的类加载器。
- 如果父类加载器可以完成类的加载,则由父类加载器加载该类,如果父类加载器无法加载,子加载器才会尝试自己加载
以上整个过程称为双亲委派机制。
示例1
public static void main(String[] args) throws ClassNotFoundException {String classRootPath = "D:\\work\\code\\mytest\\peixun\\target\\classes";// 同一个 ClassLoader 实例,加载同一个 Class ,得到的是同一个 Class 对象ClassLoader my = new MyClassLoader(classRootPath);Class a = my.loadClass("com.yyoo.jvm.MyEmp");Class b = my.loadClass("com.yyoo.jvm.MyEmp");System.out.println(a == b);// true// 同一个 ClassLoader 的不同实例,加载同一个 Class 文件,得到的也是同一个 Class 对象ClassLoader my1 = new MyClassLoader(classRootPath);Class c = my1.loadClass("com.yyoo.jvm.MyEmp");System.out.println(a == c);// true// 不同的 ClassLoader ,加载同一个 Class 文件,得到的也是同一个 Class 对象ClassLoader app = ClassLoader.getSystemClassLoader();Class d = app.loadClass("com.yyoo.jvm.MyEmp");System.out.println(a == d);// trueSystem.out.println(a.getClassLoader());// 系统类加载器System.out.println(d.getClassLoader());// 系统类加载器}
注:MyClassLoader 即为我们上面自定义的 ClassLoader。
根据双亲委派机制来解释该现象,我们自定义加载器的 classRootPath 其实就是我们应用的 classPath,而 classPath 下的类是由系统类加载器加载的,而且其加载的始终是 classPath 下的类,而我们的 ClassRootPath 下的类永远不会加载(除非我们自定义加载的类和系统类加载器加载的类全类名有不同的地方 或者 classPath 下没有该类)。
示例2
示例1的前提是,MyEmp 的 .class 文件存在于我们应用的 classPath 路径下,示例 2 ,我们将 classPath 路径下的 .class 文件删除,并按包路径创建文件夹在 D 盘根路径下(Java 的几大类加载器加载的路径和目录之外),再次执行
String classRootPath = "D:";
// 同一个 ClassLoader 实例,加载同一个 Class ,得到的是同一个 Class 对象
ClassLoader my = new MyClassLoader(classRootPath);
Class a = my.loadClass("com.yyoo.jvm.MyEmp");
Class b = my.loadClass("com.yyoo.jvm.MyEmp");System.out.println(a == b);// 同一个 ClassLoader 的不同实例,加载同一个 Class 文件,得到的不是同一个 Class 对象
ClassLoader my1 = new MyClassLoader(classRootPath);
Class c = my1.loadClass("com.yyoo.jvm.MyEmp");
System.out.println(a == c);System.out.println(a.getClassLoader());
System.out.println(c.getClassLoader());
结果:
true
false
com.yyoo.jvm.MyClassLoader@3f99bd52
com.yyoo.jvm.MyClassLoader@3a71f4dd
可以看到在 系统类加载器、扩展类加载器、启动类加载器加载的路径之外的 .class 文件会使用我们自定义的加载器加载,且不同的 MyClassLoader 实例加载的 Class 对象不是同一个。
双亲委派机制的好处
- 避免类的重复加载
- 保护程序安全,防止核心 API (Java 核心类)被随意篡改
Java 的 SPI 机制
SPI(service provider interface)是 jdk 内置的一种服务提供发现机制。可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,常用的关系型数据有 Mysql、Oracle、SQLServer、DB2 等,这些不同类型的数据库使用的驱动程序各不相同,那 JDK 不可能把所有厂商的驱动都实现,只能制定一个标准接口,其他不同厂商可以针对同一接口做出不同的实现,各个数据库厂商根据标准来实现自己的驱动,这就是SPI机制。
通俗点来说,SPI 就是某个应用程序只提供接口规则,具体的实现需要调用方(通常该接口会有多个实现,否则也就用不着 SPI 了)在使用时自行实现,其实现方式遵循 Java 的 SPI 机制。
比如:我们要提供一个文件上传的接口,其实现有如下几种方式:服务器本地存储、上传到 ftp 服务器、上传到 minio、上传到云服务等等
1. 接口定义
public interface FileUpload {/*** 上传文件*/void upload();}
2. 具体实现
public class LocalFileUpload implements FileUpload{@Overridepublic void upload() {System.out.println("将文件存储到服务器本地路径下");}
}
public class MinIOFileUpload implements FileUpload{@Overridepublic void upload() {System.out.println("将文件上传到 MinIOn 服务器");}
}
3. 配置 META-INF/services 文件
在应用的 META-INF/services/ 目录下(目录不存在自行创建即可),创建文件名称为 com.yyoo.spi.FileUpload 的文本文件
文件内容为:
com.yyoo.spi.LocalFileUpload
com.yyoo.spi.MinIOFileUpload
4. 接口的使用
// load 方法,如果不传 ClassLoader 则默认使用当前线程上下文的 Thread.currentThread().getContextClassLoader()
// 这里即是系统类加载器,我们也可以使用重载方法 load(Class<S> service,ClassLoader loader) 来指定加载器
ServiceLoader<FileUpload> serviceLoader = ServiceLoader.load(FileUpload.class);// 获取迭代器或者直接使用 foreach 语句即可获取 META-INF/services/com.yyoo.spi.FileUpload 文件中配置的所有实现
for (FileUpload fileUpload : serviceLoader){System.out.println(fileUpload.getClass());System.out.println(fileUpload.getClass().getClassLoader());// 使用系统类加载器加载fileUpload.upload();
}
执行结果:
class com.yyoo.spi.LocalFileUpload
sun.misc.Launcher$AppClassLoader@18b4aac2
将文件存储到服务器本地路径下
class com.yyoo.spi.MinIOFileUpload
sun.misc.Launcher$AppClassLoader@18b4aac2
将文件上传到 MinIOn 服务器
ServiceLoader 是 java.util 包提供的工具类,其主要作用就是通过读取 META-INF/services 下的配置,然后根据配置通过系统类加载器加载对应配置的实现类。
SPI 允许应用程序外部提供内部接口的实现,从而改变了内部接口的行为,在某种程度上规避了双亲委派机制思想(核心 API 实现被改变)。
相关文章:

JVM 类的加载子系统
文章目录 类的加载过程加载阶段链接阶段初始化 类的加载器测试代码中获取对应的加载器获取加载器加载的路径不同类对应的加载器自定义加载器自定义加载器的方式 获取类的加载器的方式双亲委派机制双亲委派机制的好处 Java 的 SPI 机制1. 接口定义2. 具体实现3. 配置 META-INF/s…...
什么是1024程序员节
一年一度专属于程序员的节日“1024程序员节”要到来了,相信有很多的小伙伴跟我一样,对这个节日非常的熟悉,但也有一下小伙伴对这个节日非常陌生,没事,下面由我来讲解一下1024程序员节。 目录 节日背景 节日由来 社…...

spark获取hadoop服务token
spark 作业一直卡在accepted 问题现象问题排查1.查看yarn app日志2.问题分析与原因 问题现象 通过yarn-cluster模式提交spark作业,客户端日志一直卡在submit app,没有运行 问题排查 1.查看yarn app日志 appid已生成,通过yarn查看app状态为…...

Simulink 最基础教程(一)
1.1基本概念 一个典型的Simulink模型大致如上图这样: 1)模块 block:图中画圈的那些,每个模块可以完成一些特定的任务,类似MATLAB中函数的概念。软件提供了很多模块,当然也可以自定义新的模块 2࿰…...
微信小程序:单行输入和多行输入组件
微信小程序提供了两种输入类型的输入框组件,分别是单行输入框 <input> 和多行输入框 <textarea>。 1. 单行输入组件(input) 单行输入框 <input> <input> 是一个用于收集用户输入的组件,主要用于收集单行…...
1024程序员
听说今天可以拿勋章,嘿嘿...
【Segment Anything Model】八:修改SAM源码做分类任务
🍉 博主微信 cvxiayixiao 🍓 【Segment Anything Model】计算机视觉检测分割任务专栏。 链接 🍑 【公开数据集预处理】特别是医疗公开数据集的接受和预处理,提供代码讲解。链接 🍈 【opencv+图像处理】opencv代码库讲解,结合图像处理知识,不仅仅是调库。链接 文章目…...

Java后端开发——实现登录验证程序
一、实现一个简单登录验证程序 实现一个简单的用户登录验证程序,如果用户名是 abc ,密码是 123,则显示欢迎用户的信息,否则显示“用户名或密码不正确”。 【分析】 该案例采用 JSP 页面只完成提交信息和验证结果的显示ÿ…...
CSS高频面试题
1.行内元素有哪些?块级元素有哪些?空元素有哪些?CSS的盒模型? 块级元素:div, p, h1-h6,form, ul ,li行内元素:a, b, br, span, i, input, select行内块级元素:img , input空元素:即没有内容的HTML元素,…...

解决matlab报错“输入参数的数目不足”
报错语句:tanh((peakNums-parameter)/2) 报错提示:输入参数的数目不足 运行环境:matlab2021b 分析原因: 当执行peakNums - parameter时,如果peakNums和parameter都是向量,那么这并不一定意味着会得到对应…...
使用python_opencv比较图像差异/使用python_opencv找出两张图像的差异范围
目录 1 创建conda环境 2 安装python库 2.1 报错 ModuleNotFoundError: No module named numpy 3 image_diff.py...

NOIP2023模拟1联测22 爆炸
NOIP2023模拟1联测22 爆炸 题目大意 自己看 思路 当一个炸弹被引爆后,它的方向是固定的。如果被竖着引爆,那么应该选择横着引爆,否则选择竖着引爆,这是显然 的。 考虑对于每个炸弹 ( i , j ) (i , j) (i,j) 将第 i i i 行…...

http post协议实现简单的rpc协议,WireShark抓包分析
文章目录 1.http 客户端-RPC客户端1.http 服务端-RPC服务端3.WireShark抓包分析3.1客户端到服务端的HTTP/JSON报文3.2服务端到客户端的HTTP/JSON报文 1.http 客户端-RPC客户端 import json import requests# 定义 RPC 客户端类 class RPCClient:def __init__(self, server_url…...
1024程序员节
一年一年真快啊,...
嵌入式--->怎样选择编译语言,C C++或是Rust?
C 老牌语言,不可替代,速度和资源占用都是嵌入式领域着重考虑的 Rust 作为新生语言,已经成长到可以和C进行竞争的地步,不论是速度还是资源占用看,还是安全性 C 嵌入式开发使用C的思想,可以极大地简化代码&am…...

一起学数据结构(12)——归并排序的实现
1. 归并排序原理: 归并排序的大概原理如下图所示: 从图中可以看出,归并排序的整体思路就是把已给数组不断分成左右两个区间,当这个区间中的数据数量到达一定数值时,便返回去进行排序,整体的结构类似二叉树…...

读书笔记之《敏捷测试从零开始》(一)
大家好,我是rainbowzhou。 子曰:学而时习之,不亦说乎?今天我想和大家分享一本测试书籍——《敏捷测试从零开始》。以下为我的读书笔记: 精彩片段摘录: 焦虑往往来自于对比,当你在自己的圈子里面…...
视频亮度太低了,如何调高
充足、均匀、稳定的光亮对于视频制作是至关重要的,在现实生活中,不可预见的天气变化和拍摄地方的阴暗常常给我们留下暗淡无光的视频片段。 如果你的视频太暗想知道如何使它变亮,且又不知道使用哪个工具,那你无需担心,因…...

Xubuntu16.04系统中安装create_ap创建无线AP
1.背景说明 在Xubuntu16.04系统的设备上安装无线WIFI模块后,想通过设备自身的无线AP,进行和外部设备的连接,需要安装create_ap软件,并设置无线AP的名称和密码,并设置为开机自启动。 create_ap是一个用于在Linux系统上创…...
WPF 设置全局静态参数
可以使用system官方库来设置参数 引入system xmlns:system"clr-namespace:System;assemblymscorlib"设置参数资源 <Window.Resources><system:Double x:Key"ButtonWidth">90</system:Double></Window.Resources>使用参数资源 &l…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...