JVM—类加载器、双亲委派机制

目录

什么是类加载器

类加载器的分类

Bootstrap启动类加载器

通过启动类加载器加载用户jar包

Extension扩展类加载器和Application应用程序类加载器

通过扩展类加载器加载用户jar包

双亲委派机制

打破双亲委派机制

自定义类加载器

线程上下文类加载器

Osgi框架的类加载器

总结

JDK9之后的类加载器


什么是类加载器

类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术,类加载器只参与加载过程中的字节码获取并加载到内存这一部分。

类加载器会通过二进制流的方式获取到字节码文件的内容,接下来将获取到的数据交给Java虚拟机,虚拟机会在方法区和堆上生成对应的对象保存字节码信息。

类加载器的分类

类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现的。

1. JDK中默认提供或者自定义:JDK中默认提供了多种处理不同渠道的类加载器,程序员也可以自己根据需求定制,使用Java语言。所有Java中实现的类加载器都需要继承ClassLoader这个抽象类。
2. 虚拟机底层实现:源代码位于Java虚拟机的源码中,实现语言与虚拟机底层语言一致,比如Hotspot使用C++。主要目的是保证Java程序运行中基础类被正确地加载,比如java.lang.String,Java虚拟机需要确保其可靠性。

类加载器的设计JDK8和8之后的版本差别较大,首先来看JDK8及之前的版本,这些版本中默认的类加载器有如下几种:


Bootstrap启动类加载器

1. 启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C++编写的类加载器。
2. 默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar,resources.jar等。

/*** 启动程序类加载器案例*/
public class BootstrapClassLoaderDemo {public static void main(String[] args) throws IOException {ClassLoader classLoader = String.class.getClassLoader();System.out.println(classLoader);System.in.read();}
}

这段代码通过String类获取到它的类加载器(Bootstrap)并且打印,结果是null。这是因为启动类加载器在JDK8中是由C++语言来编写的,在Java代码中去获取既不适合也不安全,所以返回null。

通过启动类加载器加载用户jar包

如果用户想扩展一些比较基础的jar包,并让启动类加载器加载,有以下两种途径。
1. 放入jre/lib下进行扩展(不推荐):尽可能不要去更改JDK安装目录中的内容,会出现即时放进去由于文件名不匹配的问题也不会正常地被加载;
2. 使用参数进行扩展(推荐):使用-Xbootclasspath/a:jar包目录/jar包名 进行扩展,参数中的/a代表新增。
如下图,在IDEA配置中添加虚拟机参数,就可以加载D:/jvm/jar/classloader-test.jar这个jar包了。


Extension扩展类加载器和Application应用程序类加载器

1. ClassLoader类定义了具体的行为模式,简单来说就是先从本地或者网络获得字节码信息,然后调用虚拟机底层的方法创建方法区和堆上的对象。这样的好处就是让子类只需要去实现如何获取字节码信息这部分代码;
2. SecureClassLoader提供了证书机制,提升了安全性;
3. URLClassLoader提供了根据URL获取目录下或者指定jar包进行加载,获取字节码的数据;
4. 扩展类加载器和应用程序类加载器继承自URLClassLoader,获得了上述的三种能力。

通过扩展类加载器加载用户jar包

1. 放入/jre/lib/ext下进行扩展(不推荐):尽可能不要去更改JDK安装目录中的内容;
2. 使用参数进行扩展使用参数进行扩展(推荐):使用-Djava.ext.dirs=jar包目录 进行扩展,这种方式会覆盖掉原始目录,可以用;(windows系统)或:(macos/linux系统)符号进行分隔追加上原始目录,如下图。

使用引号将整个地址包裹起来,这样路径中即便是有空格也不需要额外处理。路径中要包含原来ext文件夹,同时在最后加上扩展的路径。


双亲委派机制


面试题1:什么是双亲委派机制
1、当一个类加载器在加载某个类时,会先自底向上查找是否有加载器加载过,如果加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载;
2、应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。

面试题2:双亲委派的作用/好处有哪些?
1.保证类加载的安全性。通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性;
2.避免重复加载。双亲委派机制可以避免同一个类被多次加载。

面试题3:如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载?
答:由启动类加载器加载,根据双亲委派机制,它的优先级是最高的。

面试题4:String类能被覆盖吗,在自己的项目中创建一个java.lang.String类,会被加载吗?
不会,因为存在双亲委派机制,类不会被重复加载,会返回启动类加载器加载在rt.jar包中的String类。


打破双亲委派机制

打破双亲委派机制历史上有如下三种方式,但本质上只有第一种算是真正的打破了双亲委派机制。
1. 自定义类加载器并且重写loadClass方法。Tomcat通过这种方式实现应用之间类隔离;
2. 线程上下文类加载器。利用上下文类加载器加载类,比如JDBC和JNDI等;
3. Osgi框架的类加载器。历史上Osgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载,目前很少使用。

自定义类加载器

        一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类,Tomcat就需要保证这两个类都能加载并且它们应该是不同的类。如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了。
        那么自定义加载器是如何做到打破双亲委派机制的呢?首先我们需要了解双亲委派机制的代码到底在哪里,接下来只需要把这段代码消除即可。
        ClassLoader中包含了4个核心方法,双亲委派机制的核心代码就位于loadClass方法中。

//类加载的入口,提供了双亲委派机制。内部会调用findClass [重要]
public Class<?> loadClass(String name)//由类加载器子类实现,获取二进制数据调用defineClass
//比如URLClassLoader会根据文件路径去获取类文件中的二进制数据。[重要]
protected Class<?> findClass(String name)//做一些类名的校验,然后调用虚拟机底层的方法将字节码信息加载到虚拟机内存中
protected final Class<?> defineClass(String name, byte[] b, int off, int len)//执行类生命周期中的连接阶段
protected final void resolveClass(Class<?> c)

loadClass方法核心代码如下

//先查找是否加载过,加载过就返回
Class<?> c = findLoadedClass(name);//如果没有加载过,则委派给父类加载
if(c == null){//parent等于null则说明父类加载器是启动类加载器if(parent != null){c = parent.loadClass(name,false);//由父类加载elsec = findBootstrapClassOrNull(name);//由启动类加载器加载//若父类加载器无法加载,则由本加载器加载if(c == null)c = findClass(name);}return c;

自定义加载器通过重写 loadClass 方法,清除了其中有关双亲委派机制的逻辑,因此打破了双亲委派机制。
按照loadClass方法的逻辑,如果父类加载失败,会调用自己的findClass方法来完成类的加载。如果用户在实现自定义类加载器时,希望按照自己的意愿去加载类,但又想保证自定义类加载器是符合双亲委派机制的,则可以重写findClass方法,在该方法中实现类的加载逻辑,而不必重写loadClass方法,从而保留了双亲委派机制的逻辑。

自定义类加载器打破双亲委派机制代码如下

package classloader.broken;//package com.itheima.jvm.chapter02.classloader.broken;import org.apache.commons.io.IOUtils;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.ProtectionDomain;
import java.util.regex.Matcher;/*** 打破双亲委派机制 - 自定义类加载器*/public class BreakClassLoader1 extends ClassLoader {private String basePath;private final static String FILE_EXT = ".class";//设置加载目录public void setBasePath(String basePath) {this.basePath = basePath;}//使用commons io 从指定目录下加载文件private byte[] loadClassData(String name)  {try {String tempName = name.replaceAll("\\.", Matcher.quoteReplacement(File.separator));FileInputStream fis = new FileInputStream(basePath + tempName + FILE_EXT);try {return IOUtils.toByteArray(fis);} finally {IOUtils.closeQuietly(fis);}} catch (Exception e) {System.out.println("自定义类加载器加载失败,错误原因:" + e.getMessage());return null;}}//重写loadClass方法,不再走双亲委派机制@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {//在加载默认的Object父类时,需要交由父类加载if(name.startsWith("java.")){return super.loadClass(name);}//从磁盘中指定目录下加载byte[] data = loadClassData(name);//调用虚拟机底层方法,方法区和堆区创建对象return defineClass(name, data, 0, data.length);}public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {//第一个自定义类加载器对象BreakClassLoader1 classLoader1 = new BreakClassLoader1();classLoader1.setBasePath("D:\\lib\\");Class<?> clazz1 = classLoader1.loadClass("com.itheima.my.A");//第二个自定义类加载器对象BreakClassLoader1 classLoader2 = new BreakClassLoader1();classLoader2.setBasePath("D:\\lib\\");Class<?> clazz2 = classLoader2.loadClass("com.itheima.my.A");System.out.println(clazz1 == clazz2);Thread.currentThread().setContextClassLoader(classLoader1);System.out.println(Thread.currentThread().getContextClassLoader());System.in.read();}
}

默认情况下自定义类加载器的父类加载器是应用程序类加载器。

两个自定义类加载器加载相同限定名的类,不会冲突吗?
不会冲突,在同一个Java虚拟机中,只有相同类加载器+相同的类限定名才会被认为是同一个类。


线程上下文类加载器

利用线程上下文类加载器加载类,比如JDBC和JNDI等,现对JDBC案例进行讨论,首先需要介绍java SPI机制。

Java SPI机制
定义:
SPI 是一种基于接口的编程模式,它允许服务提供者在不修改原有系统代码的情况下,通过实现特定接口并将其部署到应用程序的类路径下,从而被应用程序自动加载和使用。服务使用者只需定义接口规范并由服务提供者负责对接口进行实现,服务使用者不用关心具体的实现细节。这使得代码的维护和升级更加容易,降低了模块之间的耦合度。

Java SPI 的详细工作原理如下
1. 定义服务接口: 首先,定义一个接口,这个接口将作为服务的规范,规定所有实现类必须遵循的方法;
2. 实现服务接口: 接着,创建一个或多个接口实现类,每个实现类代表一个具体的服务提供者;
3. 注册服务实现: 在实现类的JAR 包中,创建一个 META-INF/services/ 目录,并在其中创建一个以服务接口全名命名的文件。文件内容包含实现类的全名,每行一个;
4. 加载和使用服务: 使用 ServiceLoader 类来加载这些服务实现。ServiceLoader 会查找 META-INF/services 目录中的服务定义,并加载所有实现类。

在 Java 中,数据库连接是一个典型的 SPI 应用场景。Java 的java.sql.Driver接口就是一个服务接口。不同数据库厂商(如 MySQL、Oracle、SQL Server 等)提供了各自的Driver实现类。
JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql驱动、oracle驱动。
DriverManager类由JDK提供,位于rt.jar包中,由启动类加载器加载。DriverManager类需要去加载服务提供者引入的jar包中的驱动类(SPI机制),而jar包中的驱动类需要委派应用程序类加载器去加载,这种父类加载器去请求子类加载器完成类的加载行为,打破了双亲委派机制。Java中涉及SPI的加载基本上都采用这种方式来完成。



Osgi框架的类加载器

历史上,OSGi模块化框架存在同级之间的类加载器的委托加载。OSGi还使用类加载器实现了热部署的功能。热部署指的是在服务不停止的情况下,动态地更新字节码文件到内存中。

以下内容节选自《深入理解 Java 虚拟机》
总结:在 OSGi 环境下,类加载器不再遵循双亲委派模型所推荐的树状结构,而是拥有一套自身的类加载规则,其中部分规则打破了类的双亲委派机制。


总结

在上述介绍的三种打破双亲委派机制的方式中,本质上只有第一种方式算是真正打破了该机制。这是因为双亲委派机制的核心代码在 loadClass 中,而只有第一种方式重写了 loadClass 方法,清除了其中有关双亲委派机制的逻辑。

至于其他两种方式,从宏观层面,也就是类的调用层面来看,违背了双亲委派机制。但这两种方式并没有重写loadClass方法,并未清除有关双亲委派机制逻辑的代码,所以从单个类的角度而言,是符合双亲委派机制的。


JDK9之后的类加载器

JDK8及之前的版本中,扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java。

由于JDK9引入了module的概念,类加载器在设计上发生了很多变化。
1.启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中。
Java中的BootClassLoader继承自BuiltinClassLoader,实现从模块中找到要加载的字节码资源文件。启动类加载器依然无法通过java代码获取到,返回的仍然是null,保持了统一。

2、扩展类加载器被替换成了平台类加载器(Platform Class Loader)。
平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是为了与老版本的设计方案兼容,自身没有特殊的逻辑。

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

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

相关文章

flask第一个应用

文章目录 安装一、编程第一步二、引入配置三、代码解析 安装 python环境安装的过程就不重复赘述了&#xff0c;flask安装使用命令pip install Flask即可&#xff0c;使用命令pip show Flask查看flask版本信息 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供…

享元模式-实现大颗粒度对象缓存机制

详解 享元模式是一种结构型设计模式&#xff0c;其主要目的是通过共享尽可能多的相同部分来有效地支持大量细粒度的对象。它通过将对象的属性分为内在属性&#xff08;可以共享、不随环境变化的部分&#xff09;和外在属性&#xff08;根据场景变化、不能共享的部分&#xff0…

Flutter学习笔记(一)-----环境配置

一、android 环境 android这边可以参照godot的配置 1.装java Java Downloads | Oracle x64 Compressed Archive &#xff1a;下载后直接解压到某个位置&#xff0c;不用安装 x64 installer: 下载后双击安装 注意&#xff1a;不要去百度直接搜Java安装&#xff0c;这样你最多安…

JetBrains Clion Idea 等缓存文件和配置文件迁移

JetBrains 缓存文件和配置文件迁移 文件默认路径 缓存文件默认路径&#xff1a; %userprofile%/AppData/Local/JetBrains/应用名 如 C:/Users/wbl/AppData/Local/JetBrains/CLion2021.3日志文件默认路径&#xff1a;默认在配置文件目录下的log文件夹 %userprofile%/AppData…

《AI产品经理手册》——解锁AI时代的商业密钥

在当今这个日新月异的AI时代&#xff0c;每一位产品经理都面临着前所未有的挑战与机遇&#xff0c;唯有紧跟时代潮流&#xff0c;深入掌握AI技术的精髓&#xff0c;才能在激烈的市场竞争中独占鳌头。《AI产品经理手册》正是这样一部为AI产品经理量身定制的实战宝典&#xff0c;…

2024年最全2024年最系统的网络安全自学路线,学完即可就业_安全学习路线(2),2024年最新你掌握了多少

一个人可以走的很快&#xff0c;但一群人才能走的更远&#xff01;不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人&#xff0c;都欢迎加入我们的的圈子&#xff08;技术交流、学习资源、职场吐槽、大厂内推、面试辅导&#xff09;&#xff0c;让我们一起学习成长&#xf…

前端拖拽库方案之react-beautiful-dnd

近期&#xff0c;知名 React 拖拽库 react-beautiful-dnd 宣布了项目弃用的决定&#xff0c;未来将不再维护。这一决定源于其存在的缺陷与局限性&#xff0c;促使作者转向开发一个更加现代化的拖拽解决方案——Pragmatic drag and drop&#xff08;下面会介绍&#xff09;&…

【深度学习】实验 — 动手实现 GPT【四】:代码实现Transformer、代码实现GPT模型、训练大型语言模型(LLM)

【深度学习】实验 — 动手实现 GPT【四】&#xff1a;代码实现Transformer、代码实现GPT模型、训练大型语言模型&#xff08;LLM&#xff09; 在 Transformer 块中连接注意力层和线性层代码实现Transformer 块 代码实现GPT模型文本生成训练模型计算训练集和验证集的损失 训练大…

我在命令行下剪辑视频

是的&#xff0c;你不需要格式工厂&#xff0c;你也不需要会声会影&#xff0c;更不需要爱剪辑这些莫名其妙的流氓软件&#xff0c;命令行下视频处理&#xff0c;包括剪辑&#xff0c;转码&#xff0c;提取&#xff0c;合成&#xff0c;缩放&#xff0c;字幕&#xff0c;特效等…

海外云手机是什么?对外贸电商有什么帮助?

在外贸电商领域&#xff0c;流量引流已成为卖家们关注的核心问题。越来越多的卖家开始利用海外云手机&#xff0c;通过TikTok等社交平台吸引流量&#xff0c;以推动商品在海外市场的销售。那么&#xff0c;海外云手机到底是什么&#xff1f;它又能为外贸电商卖家提供哪些支持呢…

MATLAB绘图|关于三维制图,给初学者的建议

给MATLAB的关于绘制三维图的建议 文章目录 基础知识使用基本函数设置轴标签和标题调整视角添加网格和图例绘制子图灵活使用 hold on 和 hold off保存图形总结 基础知识 了解三维坐标系统&#xff1a;三维图形有三个轴&#xff08;x、y、z&#xff09;&#xff0c;确保你理解如…

centos7配置keepalive+lvs

拓扑图 用户访问www.abc.com解析到10.4.7.8&#xff0c;防火墙做DNAT将访问10.4.7.8:80的请求转换到VIP 172.16.10.7:80&#xff0c;负载均衡器再将请求转发到后端web服务器。 实验环境 VIP&#xff1a;负载均衡服务器的虚拟ip地址 LB &#xff1a;负载均衡服务器 realserv…

opencv python笔记

OpenCV课程 OpenCV其实就是一堆C和C语言的源代码文件,这些源代码文件中实现了许多常用的计算机视觉算法。 OpenCV的全称是Open Source Computer Vision Library,是一个开放源代码的计算机视觉库OpenCV最初由英特尔公司发起并开发,以BSD许可证授权发行,可以在商业和研究领域中…

金融标准体系

目录 基本原则 标准体系结构图 标准明细表 金融标准体系下载地址 基本原则 需求引领、顶层设计。 坚持目标导向、问题导向、结果 导向有机统一&#xff0c;构建支撑适用、体系完善、科学合理的金融 标准体系。 全面系统、重点突出。 以金融业运用有效、保护有力、 管理高…

(实战)WebApi第10讲:Swagger配置、RESTful与路由重载

一、Swagger配置 1、导入SwashBuckle.AspNetCore包 2、在.NET Core 5框架里的startup.cs文件里配置swagger 3、在.NET Core 6框架里的Program.cs文件里配置swagger 二、RESTful风格&#xff1a;路由重载&#xff0c;HttpGet()括号中加参数 &#xff08;1&#xff09;原则&…

[java][基础]JSP

目标&#xff1a; 理解 JSP 及 JSP 原理 能在 JSP中使用 EL表达式 和 JSTL标签 理解 MVC模式 和 三层架构 能完成品牌数据的增删改查功能 1&#xff0c;JSP 概述 JSP&#xff08;全称&#xff1a;Java Server Pages&#xff09;&#xff1a;Java 服务端页面。是一种动态的…

数智税务 | 数电票:带来税务管理五大新挑战、绘就智慧税务征管新蓝图

目录 数电票&#xff0c;带来税务管理五大新挑战 1“集全” 2“管全” 3“算全” 4“备全” 5“控全” 数电票&#xff0c;绘就智慧税务征管新蓝图 1两化 2三端 3四融合 4变革征管方式 5优化征管流程 6提升征管效能 结语 数电票&#xff0c;带来税务管理五大新挑…

氢氧化铝改性打散机、分散机、包覆机、球磨机

表面改性是指在氢氧化铝颗粒表面吸附或包覆一层或多层物质&#xff0c;以改变其表面性质&#xff0c;增强其与基体材料的相容性和界面结合力。 表面改性方法主要分为物理法和化学法&#xff1a; 1.物理法&#xff1a;使用表面活性剂如高级脂肪酸、醇、胺、酯等进行表面包覆处…

【深度学习】Bert下载和使用(以bert-base-uncased为例)

【深度学习】Bert下载和使用&#xff08;以bert-base-uncased为例&#xff09; 代码报错报错原因解决方法解决步骤1.进入Hugging Face&#xff0c;检索bert-base-uncased2.点击Files and versions3.下载文件4.下载的文件放入文件夹5.代码修改 代码报错 bert BertModel.from_p…

JupyterLab,极其强大的下一代notebook!

JupyterLab简介 JupyterLab是Jupyter主打的最新数据科学生产工具&#xff0c;某种意义上&#xff0c;它的出现是为了取代Jupyter Notebook。不过不用担心Jupyter Notebook会消失&#xff0c;JupyterLab包含了Jupyter Notebook所有功能。 JupyterLab作为一种基于web的集成开发环…