JVM系列(三) -类加载器及双亲委派模型介绍

在之前的文章中,介绍了类的加载过程中,我们有提到在加载阶段,通过一个类的全限定名来获取此类的二进制字节流操作,其实类加载器就是用来实现这个操作的。

在虚拟机中,任何一个类,都需要由加载它的类加载器和这个类本身一同确立其唯一性,每一个类加载器,都拥有一个独立的类名称空间,对于类也同样如此

简单的说,在虚拟机中看两个类是否相同,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源于同一个.class文件,被同一个虚拟机加载,但是它们的类加载器不同,这两个类必定不相等。

当年为了满足浏览器上 Java Applet 的需求,Java 的开发团队设计了类加载器,它独立于 Java 虚拟机外部,同时也允许用户按自身需要自行实现类加载器。通过类加载器,可以让同一个类可以实现访问隔离、OSGi、程序热部署等等场景。发展至今,类加载器已经是 Java 技术体系的一块重要基石。

一、类加载器介绍

如果要查找类加载器,通过Thread.currentThread().getContextClassLoader()方法可以获取。

简单示例如下:

public class ClassLoaderTest {public static void main(String[] args) {ClassLoader loader = Thread.currentThread().getContextClassLoader();System.out.println("current loader:" +  loader);System.out.println("parent loader:" +  loader.getParent());System.out.println("parent parent loader:" +  loader.getParent().getParent());}
}

输出结果如下:

current loader:sun.misc.Launcher$AppClassLoader@18b4aac2
parent loader:sun.misc.Launcher$ExtClassLoader@511d50c0
parent parent loader:null

从运行结果可以看到,当前的类加载器是AppClassLoader,它的上一级是ExtClassLoader,再上一级是null

其实ExtClassLoader的上一级是有类加载器的,它叫Bootstrap ClassLoader,是一个启动类加载器,由 C++ 实现,不是 ClassLoader 子类,因此以 null 作为结果返回。

这几种类加载器的层次关系,可以用如下图来描述。

它们之间的启动流程,可以通过以下内容来简单描述:

  • 1.在虚拟机启动后,会优先初始化Bootstrap Classloader
  • 2.接着Bootstrap Classloader负责加载ExtClassLoader,并且将 ExtClassLoader的父加载器设置为Bootstrap Classloader
  • 3Bootstrap Classloader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为 ExtClassLoader

因此,在加载 Java 应用程序中的class文件时,这里的父类加载器并不是通过继承关系来实现的,而是互相配合进行加载。

站在虚拟机的角度,只存在两种不同的类加载器:

  • 启动类加载器:它由 C++ 实现(这里仅限于 Hotspot,不同的虚拟机可能实现不太一样),是虚拟机自身的一部分
  • 其它类加载器:这些类加载器都由 Java 实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,比如ExtClassLoaderAppClassLoader等,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类

站在开发者的角度,类加载器大致可以划分为三类:

  • 启动类加载器:比如Bootstrap ClassLoader,负责加载<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数制定的路径,例如jre/lib/rt.jar里所有的class文件。同时,启动类加载器是无法被 Java 程序直接引用的
  • 拓展类加载器:比如Extension ClassLoader,负责加载 Java 平台中扩展功能的一些 jar 包,包括<JAVA_HOME>\lib\ext目录中或java.ext.dirs指定目录下的 jar 包。同时,开发者可以直接使用扩展类加载器
  • 应用程序类加载器:比如Application ClassLoader,负责加载ClassPath路径下所有 jar 包,如果应用程序中没有自定义过自己的类加载器,一般情况下它就是程序中默认的类加载器

当然,如果有必要,也可以自定义类加载器,因为 JVM 自带的 ClassLoader 只懂得从本地文件系统中加载标准的class文件,如果要从特定的场所取得class文件,例如数据库中和网络中,此时可以自己编写对应的 ClassLoader 类加载器。

二、双亲委派模型

在上文中我们提到,在虚拟机中,任何一个类由加载它的类加载器和这个类一同来确立其唯一性

也就是说,JVM 对类的唯一标识,可以简单的理解为由ClassLoader id + PackageName + ClassName组成,因此在一个运行程序中有可能存在两个包名和类名完全一致的类,但是如果这两个类不是由一个 ClassLoader 加载,会被视为两个不同的类,此时就无法将一个类的实例强转为另外一个类,这就是类加载器的隔离性。

为了解决类加载器的隔离问题,JVM 引入了双亲委派模型

双亲委派模式,可以用一句话来说表达:任何一个类加载器在接到一个类的加载请求时,都会先让其父类进行加载,只有父类无法加载(或者没有父类)的情况下,才尝试自己加载

大致流程图如下:

使用双亲委派模式,可以保证,每一个类只会有一个类加载器。例如 Java 最基础的 Object 类,它存放在 rt.jar 之中,这是 Bootstrap 的职责范围,当向上委派到 Bootstrap 时就会被加载。

但如果没有使用双亲委派模式,可以任由自定义加载器进行加载的话,Java 这些核心类的 API 就会被随意篡改,无法做到一致性加载效果。

JDK 中ClassLoader.loadClass()类加载器中的加载类的方法,部分核心源码如下:

public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{// 1.首先要保证线程安全synchronized (getClassLoadingLock(name)) {// 2.先判断这个类是否被加载过,如果加载过,直接跳过Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {// 3.有父类,优先交给父类尝试加载;如果为空,使用BootstrapClassLoader类加载器if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 父类加载失败,这里捕获异常,但不需要做任何处理}// 4.没有父类,或者父类无法加载,尝试自己加载if (c == null) {long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

三、自定义类加载器

在上文中我们提及过,针对某些特定场景,比如通过网络来传输 Java 类的字节码文件,为保证安全性,这些字节码经过了加密处理,这时系统提供的类加载器就无法对其进行加载,此时我们可以自定义一个类加载器来完成文件的加载。

自定义类加载器也需要继承ClassLoader类,简单示例如下:

public class CustomClassLoader extends ClassLoader {private String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {Class<?> c = findLoadedClass(name);if (c == null) {byte[] data = loadClassData(name);if (data == null) {throw new ClassNotFoundException();}return defineClass(name, data, 0, data.length);}return null;}protected byte[] loadClassData(String name) {try {// package -> file foldername = name.replace(".", "//");FileInputStream fis = new FileInputStream(new File(classPath + "//" + name + ".class"));ByteArrayOutputStream baos = new ByteArrayOutputStream();int len = -1;byte[] b = new byte[2048];while ((len = fis.read(b)) != -1) {baos.write(b, 0, len);}fis.close();return baos.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}
}

相关的测试类如下:

package com.example;public class ClassLoaderTest {public static void main(String[] args) {ClassLoader loader = Thread.currentThread().getContextClassLoader();System.out.println("current loader:" +  loader);}
}

ClassLoaderTest.java源文件放在指定目录下,并通过javac命令编译成ClassLoaderTest.class,最后进行测试。

public class CustomClassLoaderTest {public static void main(String[] args) throws Exception {String classPath = "/Downloads";CustomClassLoader customClassLoader = new CustomClassLoader(classPath);Class<?> testClass = customClassLoader.loadClass("com.example.ClassLoaderTest");Object obj = testClass.newInstance();System.out.println(obj.getClass().getClassLoader());}
}

输出结果:

com.example.CustomClassLoader@60e53b93

在实际使用过程中,最好不要重写loadClass方法,避免破坏双亲委派模型。

四、加载类的几种方式

在类加载器中,有三种方式可以实现类的加载。

  • 1.通过命令行启动应用时由 JVM 初始化加载,在上文已提及过
  • 2.通过Class.forName()方法动态加载
  • 3.通过ClassLoader.loadClass()方法动态加载

其中Class.forName()ClassLoader.loadClass()加载方法,稍有区别:

  • Class.forName():表示将类的.class文件加载到 JVM 中之后,还会对类进行解释,执行类中的static方法块;
  • Class.forName(name, initialize, loader):支持通过参数来控制是否执行类中的static方法块;
  • ClassLoader.loadClass():它只将类的.class文件加载到 JVM,但是不执行类中的static方法块,只有在newInstance()才会去执行static方法块;

我们可以看一个简单的例子!

public class ClassTest {static {System.out.println("初始化静态代码块!");}
}
public class CustomClassLoaderTest {public static void main(String[] args) throws Exception {// 获取当前系统类加载器ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 1.使用Class.forName()来加载类,默认会执行初始化静态代码块Class.forName(ClassTest.class.getName());// 2.使用Class.forName()来加载类,指定false,不会执行初始化静态代码块
//        Class.forName(ClassTest.class.getName(), false, classLoader);// 3.使用ClassLoader.loadClass()来加载类,不会执行初始化静态代码块
//        classLoader.loadClass(ClassTest.class.getName());}
}

运行结果如下:

初始化静态代码块!

切换不同的加载方式,会有不同的输出结果!

五、小结

从以上的介绍中,针对类加载器的机制,我们可以总结出以下几点:

  • 全盘负责:当一个类加载器负责加载某个Class文件时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来加载
  • 双亲委派:在接受类加载请求时,会让父类加载器试图加载该类,只有在父类加载器无法加载该类或者没有父类时,才尝试从自己的类路径中加载该类
  • 按需加载:用户创建的类,通常加载是按需进行的,只有使用了才会被类加载器加载
  • 缓存机制:有被加载过的Class文件都会被缓存,当要使用某个Class时,会先去缓存查找,如果缓存中没有才会读取Class文件进行加载。这就是为什么修改了Class文件后,必须重启 JVM,程序的修改才会生效的原因

写到最后

不会有人刷到这里还想白嫖吧?点赞对我真的非常重要!在线求赞。加个关注我会非常感激!

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

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

相关文章

《Milvus Cloud向量数据库指南》——Milvus Cloud不同场景下的部署形态选型

不同场景下的部署形态选型 一般说选型肯定离不开阶段。用到向量数据库的应用基本有这么几个阶段: AI 应用的快速原型构建。比如你在做一个 AI 个人助手、一个小的搜索引擎原型、一个端到端的 RAG 原型,这类项目的迭代速度是很关键的,而且原型构建期不需要关心性能或者稳定性…

暑假第二周任务——3Gshare的仿写

3GShare的仿写 登陆注册页面 这个界面的UI比较简单&#xff0c;比较困难的地方在于限制我们的输入长度以及我们输入的字符类型。 在这里我是通过在textField的代理中设计限定输入字符的内容&#xff0c;从而实现限制输入长度和限制输入字符的内容&#xff0c;下面给出相关的代…

Day24|二叉树 PART08

235. 二叉搜索树的最近公共祖先 与236类似但可以利用二叉搜索树的性质来做 二叉搜索树&#xff1a;左子 小于头 小于右子 &#xff08;怪怪的 感觉是不是先记住比较好&#xff09;&#xff08;而且也没太理解二叉搜索树的规律&#xff09; 大体思路&#xff1a;从上到下遍历&a…

html 解决tooltip宽度显示和文本任意位置换行文本显示问题

.el-tooltip__popper {max-width: 480px;white-space: break-spaces; /* 尝试不同的white-space属性值 */word-break:break-all; }

干货:three.js中的六大光源的知识点。

我们在二维屏幕中感知三维场景的一个最重要的要素就是光&#xff0c;光和光源是three.js中一个非常重要的知识点。本文想借着这个话题&#xff0c;为老铁们分享以下六大光源知识点&#xff1a;环境光、点光源、聚光灯、方向光、半球光、平面光。 一、点光源 在 Three.js 中&a…

模拟string(四)详解

目录 判断string大小关系bool operator(const string&s1,const string s2)代码 bool operator<(const string& s1, const string& s2)代码 bool operator<(const string& s1, const string& s2)代码 bool operator>(const string& s1, const …

Stable Diffusion WebUI本地环境搭建

一、项目代码下载 git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui 二、环境配置 conda create --n stafu python3.10.6 实际上跟自己创建的环境没有关系&#xff0c;项目启动会自动复制这个环境&#xff0c;之后项目根据这个基础环境构建 也可以在自己…

机器学习——第一章 绪论

目录 1. 1 引言 1. 2 基本术语 1.3 假设空间 1.4 归纳偏好 1. 1 引言 机器学习致力于研究如何通过计算的手段&#xff0c;利用经验来玫善系统自身的性能在计算机系统中&#xff0c;"经验"通常以"数据"形式存在&#xff0c;因此&#xff0c;机器学习所研…

由字节对齐引发的一场“血案“

最近在搞个网络通信协议&#xff0c; 采用socket udp传输&#xff0c; 运行时&#xff0c;居然报段错误了&#xff0c; 经过debug&#xff0c;发现居然是因为字节对齐问题导致的。 这个问题在实现通信协议&#xff0c;是经常会遇到的问题&#xff0c; 为了方便读者理解&am…

PSVR2下个月将正式支持PC

PlayStation VR 2将于下个月正式支持PC平台。连接PC&#xff0c;需要使用PlayStation VR2头显PC适配器&#xff0c;该适配器将于8月7日发售。 需要注意的是&#xff0c;玩家还需要一根兼容DisplayPort 1.4的线缆、一个Steam账号以及满足最低配置要求的PC。 索尼特别强调&#…

js 替换json中的转义字符 \

例如有以下字符串 "\"{\\\"account\\\":\\\"66\\\",\\\"name\\\":\\\"66\\\"}\"" 想得到如下字符串 {"account":"66","name":"66"} 执行替换字符串 "\"{…

大坝安全监测设备有哪些主要功能?

推荐型号&#xff1a;TH-WY1】大坝安全监测设备的主要功能包括以下几个方面&#xff1a; 1. **实时监测大坝的各项物理参数**&#xff1a;包括应变、位移、水位、流量等<sup>1</sup><sup>2</sup>。 2. **数据处理和分析**&#xff1a;对监测数据进行处…

热门音效、BGM哪里可以免费下载?

剪辑的奇妙世界等你探索&#xff01;在这个创意的领域里&#xff0c;音效是创造氛围、增强表现力的重要元素。我整理了8个优质的剪辑音效素材网站&#xff0c;它们提供了丰富多样的音效资源&#xff0c;无论是制作视频、音乐还是动画&#xff0c;都能为你提供所需的声音。 1、b…

单关节电机动力学辨识

这是一个单关节电机的动力学辨识过程&#xff0c;这是一个yaw轴转动电机的动力学辨识过程 1、动力学建模 &#xff08;1&#xff09;整体动力学 F J α f F J\alpha f FJαf 单关节的物理量包括惯性项、离心力和科氏力、摩擦力。这里忽略离心力和科氏力&#xff0c;据说…

信息学奥赛初赛天天练-47-CSP-J2020完善程序1-质数、因数、质因数、质因数分解算法、质因数分解算法优化

PDF文档公众号回复关键字:20240727 2020 CSP-J 完善程序1 1 完善程序 (单选题 &#xff0c;每小题3分&#xff0c;共30分) 质因数分解给出正整数 n&#xff0c;请输出将 n 质因数分解的结果&#xff0c;结果从小到大输出 例如&#xff1a;输入 n120&#xff0c;程序应该输出…

mysql报错:Unknown collation: ‘utf8mb4_0900_ai_ci‘的原因及解决方法

参考博客&#xff1a;http://t.csdnimg.cn/NRzyk 报错场景描述 使用navicate在查询中运行sql语句时报错&#xff1a;Unknown collation: utf8mb4_0900_ai_ci 报错原因 生成转储文件的数据库版本为8.0&#xff0c;我本地数据库版本为5.6&#xff0c;高版本导入到低版本&…

【C++】透析类和对象(下)

有不懂的可以翻阅我之前文章&#xff01; 个人主页&#xff1a;CSDN_小八哥向前冲 所属专栏&#xff1a;CSDN_C入门 目录 拷贝构造函数 运算符重载 赋值运算符重载 取地址运算符重载 const成员函数 取地址重载 再探构造函数 初始化列表 类型转换 static成员 友元 内…

【CN】Argo 持续集成和交付(一)

1.简介 Argo 英 [ˈɑ:ɡəu] 美 [ˈɑrˌɡo] Kubernetes 原生工具&#xff0c;用于运行工作流程、管理集群以及正确执行 GitOps。 Argo 于 2020 年 3 月 26 日被 CNCF 接受为孵化成熟度级别&#xff0c;然后于 2022 年 12 月 6 日转移到毕业成熟度级别。 argoproj.github.i…

(最全最小白易懂版)Yolov8新手教程-配置环境、数据集处理、目标检测、结果分析处理(图像指标、可视化结果)、报错分析等全过程学习记录

目录 一、安装环境&#xff08;配置yolo、demo测试&#xff09; 二、数据集准备&#xff08;格式学习&#xff09; 三、训练数据集 1.划分数据集 2.训练数据集 2.1常规训练 2.2微调 3.各种报错记录 3.1AttributeError 3.2TypeError 3.3Error while loading conda en…

Flutter Dio网络请求报错FormatException: Unexpected character

最近开发Flutter项目&#xff0c;网络请求采用的是Dio框架&#xff0c;在发起网络请求的时候报错&#xff1a; 网络请求返回的数据为&#xff1a; var returnCitySN {\"cip\": \"127.0.0.1\", \"cid\": \"00\", \"cname\"…