JVM 类的加载子系统

文章目录

  • 类的加载过程
    • 加载阶段
    • 链接阶段
    • 初始化
  • 类的加载器
    • 测试代码中获取对应的加载器
    • 获取加载器加载的路径
    • 不同类对应的加载器
    • 自定义加载器
    • 自定义加载器的方式
  • 获取类的加载器的方式
  • 双亲委派机制
    • 双亲委派机制的好处
  • Java 的 SPI 机制
    • 1. 接口定义
    • 2. 具体实现
    • 3. 配置 META-INF/services 文件
    • 4. 接口的使用

类的加载过程

在这里插入图片描述

类的加载过程分为3个阶段

  1. 加载阶段
  2. 链接阶段
  3. 初始化阶段

类的加载器,只负责加载 .class 文件,至于能不能执行,是执行引擎决定的。

加载阶段

  • 通过类的全限定名获取此类的二进制流
  • 将此类的二进制流中静态储存结构存储在运行时数据区的方法区(元空间 > 7.0 或永久代 < 7.0)
  • 在内存中生成一个 java.lang.Class 的对象,作为方法区在这个类的各种数据的访问入口

虽然一般情况下,JVM 加载的是 .class 文件,其实只要是符合 JVM 的字节码都可以进行加载。比如:.jar 包中的文件,动态代理生成的字节码(可在运行时动态生成),还比如加密的 .class 文件,通过 JVM 解密后加载,可有效防止反编译。

链接阶段

  1. 验证(Verify)
  • 每个 .class 文件都有一个特殊的开头,用以表示该文件是 JVM 支持的 .class 文件。验证的目的在于确保被加载的类的正确性。
  • 主要验证方式:文件格式验证、元数据验证、字节码验证、符号引用验证
  1. 准备(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 堆中的。
  1. 解析(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 程序中,使用引导类加载器、扩展类加载器、系统类加载器相互作用,即可。几乎不需要自定义类的加载器,我们可以在某些情况下进行自定义加载器。

  • 修改类的加载方式
  • 还比如我们对编译的源码进行了加密,在类加载时需要解密等

自定义加载器的方式

  1. 继承 ClassLoader 重写 findClass(String name) 方法
  2. 按照 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. 类的加载器收到类的加载请求时,它不会立马去加载,而且把加载请求委托给父类加载器去执行。
  2. 如果父类加载器还有父类加载器,则会进一步委托给父类加载器加载,直到最顶层的类加载器。
  3. 如果父类加载器可以完成类的加载,则由父类加载器加载该类,如果父类加载器无法加载,子加载器才会尝试自己加载

以上整个过程称为双亲委派机制。

示例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 实现被改变)。

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

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

相关文章

Redis中设置Hash数据类型的过期时间

1 方案 可以先对key进行赋值&#xff0c;然后对key设置一个过期时间。 &#xff08;1&#xff09;依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.7.3</version></dependency>…

Kubernetes (K8S)概述

目录 1、K8S 是什么&#xff1f; 1.1 作用 1.2 由来 1.3 含义 1.4 官网 2、为什么要用 K8S? 2.1 K8S 解决了裸跑Docker 的若干痛点 2.2 K8S主要功能如下 2.3 K8S 的特性 3、Kubernetes 集群架构与组件 3.1 核心组件 3.1.1 Master 组件 3.1.2 控制器主要包括 3.1…

Spring Cloud Gateway 路由构建器的源码分析

Spring Cloud Gateway 路由构建器的源码分析 文章目录 1. 路由构建器的入口2. 创建路由规则3. 设置路由规则和属性4. 路由过滤器的设置5. 构建和获取路由规则&#xff1a;6. 实例化路由构建器&#xff1a;8. 路由构建器的源码分析8.1 RouteLocator接口8.2 RouteLocatorBuilder…

判断日期区间或季节等

使用JavaScript的Date对象来获取当前日期&#xff0c;并通过比较判断是否在指定的日期范围内&#xff08;如3月16日-9月15日&#xff09;。以下是一个示例代码&#xff1a; var currentDate new Date(); // 获取当前日期 var startRange new Date(currentDate.getFullYear()…

springBoot与Vue共同搭建webSocket环境

欢迎使用Markdown编辑器 你好&#xff01; 这片文章将教会你从后端springCloud到前端VueEleementAdmin如何搭建Websocket 前端 1. 创建websocket的配置文件在utils文件夹下websocket.js // 暴露自定义websocket对象 export const socket {// 后台请求路径url: ,websocketCo…

Android前台服务和通知

前台服务 Android 13及以上系统需要动态获取通知权限。 //android 13及以上系统动态获取通知权限 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {checkPostNotificationPermission(); } private void checkPostNotificationPermission() {if (ActivityCompat.chec…

信息检索与数据挖掘 | 【实验】排名检索模型

文章目录 &#x1f4da;实验内容&#x1f4da;相关概念&#x1f4da;实验步骤&#x1f407;分词预处理&#x1f407;构建倒排索引表&#x1f407;计算query和各个文档的相似度&#x1f407;queries预处理及检索函数&#x1f525;对输入的文本进行词法分析和标准化处理&#x1f…

【Docker】Dockerfile使用技巧

开启Buildkit BuildKit是Docker官方社区推出的下一代镜像构建神器&#xff0c;可以更加快速&#xff0c;有效&#xff0c;安全地构建docker镜像。 尽管目前BuildKit不是Docker的默认构建工具&#xff0c;但是完全可以考虑将其作为Docker&#xff08;v18.09&#xff09;的首选…

一个注解让 Spring Boot 项目接口返回数据脱敏

1 背景 需求是某些接口返回的信息&#xff0c;涉及到敏感数据的必须进行脱敏操作 2 思路 ①要做成可配置多策略的脱敏操作&#xff0c;要不然一个个接口进行脱敏操作&#xff0c;重复的工作量太多&#xff0c;很显然违背了“多写一行算我输”的程序员规范。思来想去&#xff…

css如何将border线加到元素内部,占内边距,不占外边距

要将边框线添加到元素的内部&#xff0c;可以使用CSS的box-sizing属性和内边距&#xff08;padding&#xff09;来实现。 首先&#xff0c;将元素的box-sizing属性设置为border-box。这会使元素的宽度和高度包括边框和内边距在内。例如&#xff1a; .element {box-sizing: bo…

软件工程与计算总结(二十三)软件工程职业基础

本系列最后一更&#xff0c;《软计》系列总结的大结局&#xff01;本栏目告一段落&#xff0c;之后会结合真题和练习题再发布新的总结~ 往期链接&#xff1a; 《软件工程与计算》总结 一.软件工程职业 1.行业的发展 20世纪50年代&#xff1a;计算机还是研究型机器&#xff…

C++中显示构造和隐式构造

截图来源于C primerplus 第六版。

tomcat的负载均衡、动静分离(nginx联动)

动静分离&#xff1a; 访问静态页面和动态页面分开 实现动态和静态页面负载均衡 实验5台虚拟机 一、动态负载均衡 3台虚拟机模拟&#xff1a; 代理服务器&#xff1a;30 tomcat动态页面&#xff1a;21、22 代理服务器&#xff1a; proxy_pass http://tomcat; proxy_set_h…

buuctf[HCTF 2018]WarmUp 1

题目环境&#xff1a; 发现除了表情包&#xff0c;再无其他F12试试发现source.php文件访问这个文件&#xff0c;格式如下&#xff1a;url/source.php回显如下&#xff1a;PHP代码审计&#xff1a; <?php highlight_file(__FILE__); class emmm {public static function ch…

GIS在地质灾害危险性评估与灾后重建中的实践技术应用及python机器学习灾害易发性评价模型建立与优化

地质灾害是指全球地壳自然地质演化过程中&#xff0c;由于地球内动力、外动力或者人为地质动力作用下导致的自然地质和人类的自然灾害突发事件。由于降水、地震等自然作用下&#xff0c;地质灾害在世界范围内频繁发生。我国除滑坡灾害外&#xff0c;还包括崩塌、泥石流、地面沉…

我们在 Linux 环境中用 C 编程时,如果对文件读写,Linux 会自动给文件加锁嘛?

先说结论&#xff0c;结论是不会 我写了一个这样的程序 #include <stdio.h> #include <unistd.h>int main() {const char* pathname "your_file_pathname.txt";FILE* file NULL;int count 100;if(access(pathname, F_OK) 0) {file fopen(pathname…

Onnx精度转换 FP32->FP16

Onnx精度转换 FP32->FP16 1、依赖包 onnxonnxmltools 2、转换 from onnxmltools.utils.float16_converter import convert_float_to_float16 from onnxmltools.utils import load_model,save_modelonnx_model load_model("model.onnx") fp16_model convert_…

C语言进阶第九课 --------动态内存管理

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

各种添加路由的方法

Linux 篇&#xff1a; ipv4: #添加到主机的路由 # route add –host 192.168.168.110 dev eth0 # route add –host 192.168.168.119 gw 192.168.168.1 #添加到网络的路由 # route add –net IP netmask MASK eth0 # route add –net IP netmask MASK gw IP # route add –n…

k8s-----19、Helm

Helm 1、引入2、概述2.1 重点2.2 V3版本的Helm2.2.1 与之前版本的不同之处2.2.2 V3版本的运行流程 3、安装和配置仓库、一些附带操作3.1 安装3.2 配置仓库3.3 常用命令3.4 添加helm的自动补齐 4、快速部署应用(weave应用)5、 自行创建Chart5.1 Chart目录内容解析5.2 简单安装部…