JVM工作原理与实战(十二):打破双亲委派机制-自定义类加载器

专栏导航

JVM工作原理与实战

RabbitMQ入门指南

从零开始了解大数据


目录

专栏导航

前言

一、打破双亲委派机制的方法

二、自定义类加载器

1.Tomcat自定义类加载器案例

2.自定义类加载器详解

3.案例解析

总结


前言

JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了打破双亲委派机制的方法、自定义类加载器等内容。


一、打破双亲委派机制的方法

双亲委派机制的核心思想是:当一个类加载器接收到加载类的请求时,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器(Bootstrap ClassLoader)中去,只有当父类加载器无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过再由顶向下进行加载。

双亲委派机制是Java类加载器的重要特性,但在某些情况下,可能需要打破这种机制。以下是打破双亲委派机制的方法:

  • 自定义类加载器:在Java中,可以通过继承ClassLoader并重写其loadClass方法来创建自定义类加载器。通过这种方式,可以打破双亲委派机制,实现类的隔离。例如,在Tomcat中,每个Web应用都有自己的类加载器,从而实现了应用之间的类隔离。当两个Web应用中有相同限定名的类时,如Servlet类,Tomcat通过自定义类加载器保证它们是不同的类。
  • 线程上下文类加载器:在Java中,每个线程都有一个关联的上下文类加载器。通过设置线程的上下文类加载器,可以实现类的加载。例如,JDBC和JNDI等就是利用线程上下文类加载器来加载类的。
  • Osgi框架的类加载器:Osgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载。在Osgi中,每个Bundle都有自己的类加载器,当需要加载类时,会先在自己的存储空间中查找,如果找不到,则委托给父级Bundle的类加载器进行查找。这种机制打破了传统的双亲委派模型。

二、自定义类加载器

1.Tomcat自定义类加载器案例

在Tomcat环境中,一个显著的特点是其能够同时运行多个Web应用。这就引出了一个重要的问题:如果两个应用中存在相同限定名的类,例如Servlet类,那么Tomcat如何保证这两个类能够被正确加载,并且它们实际上是不同的类。

在传统的类加载机制中,双亲委派机制(Parent Delegation Mechanism)是核心。这个机制规定,当一个类加载器收到类加载请求时,它首先不会自己去加载,而是把这个请求委派给父类加载器去执行。这就形成了一个从上到下的“类加载委托层次”。然而,在多Web应用环境下,这种机制可能会导致类加载的问题。比如,当Web应用1中的MyServlet已经被其应用类加载器加载后,由于双亲委派机制的存在,Web应用2中相同限定名的MyServlet类可能就无法被其应用类加载器加载。

为了解决这个问题,Tomcat采用了一种自定义类加载器的策略。每个Web应用都有其独立的类加载器,负责加载该应用中的类。这样,即使两个应用中有相同名称的类,由于它们是由不同的类加载器加载的,因此它们实际上是不同的类。

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

2.自定义类加载器详解

ClassLoader中包含了4个核心方法,双亲委派机制的核心代码就位于loadClass方法中。

  • loadClass()是类加载的入口,提供了双亲委派机制,内部会调用findClass:
public Class<?> loadClass(String var1)

loadClass()源码:

    public Class<?> loadClass(String var1) throws ClassNotFoundException {return this.loadClass(var1, false);}protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {synchronized(this.getClassLoadingLock(var1)) {Class var4 = this.findLoadedClass(var1);if (var4 == null) {long var5 = System.nanoTime();try {if (this.parent != null) {var4 = this.parent.loadClass(var1, false);} else {var4 = this.findBootstrapClassOrNull(var1);}} catch (ClassNotFoundException var10) {}if (var4 == null) {long var7 = System.nanoTime();var4 = this.findClass(var1);PerfCounter.getParentDelegationTime().addTime(var7 - var5);PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);PerfCounter.getFindClasses().increment();}}if (var2) {this.resolveClass(var4);}return var4;}}
  • findClass()由类加载器的子类实现,其核心功能是获取二进制数据并调用defineClass ,比如URLClassLoader会根据文件路径去获取类文件中的二进制数据。
protected Class<?> findClass(String var1)
  • defineClass()会做一些类名的校验,然后调用虚拟机底层的方法将字节码信息加载到虚拟机内存中。
protected final Class<?> defineClass(String var1, byte[] var2, int var3, int var4)
  • resolveClass()会执行类生命周期中的连接阶段。
protected final void resolveClass(Class<?> var1)

loadClass()核心代码解析:

parent等于null说明父类加载器是启动类加载器,直接调用findBootstrapClassOrNull,否则调用父类加载器的加载方法。

                    if (this.parent != null) {var4 = this.parent.loadClass(var1, false);} else {var4 = this.findBootstrapClassOrNull(var1);}

父类加载器无法找到所需的类时,当前类加载器将承担起加载的责任。

                if (var4 == null) {...var4 = this.findClass(var1);...}

在实际开发中,为了正确地实现一个自定义类加载器,并确保不破坏双亲委派机制,应当重写findClass()方法。这样的做法确保了类加载请求的正确委派,同时允许开发者根据特定需求定制类加载的行为。

3.案例解析

自定义Test类:

public class Test {public static void main(String[] args) {System.out.println("自定义Test类");}
}

将Test类放到相应的目录下:

自定义类加载器:

public class BreakClassLoader extends ClassLoader {private String basePath;private final static String FILE_EXT = ".class";public void setBasePath(String basePath) {this.basePath = basePath;}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;}}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {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 {BreakClassLoader classLoader1 = new BreakClassLoader();classLoader1.setBasePath("D:\\Test\\com\\rye\\");Class<?> aClass = classLoader1.loadClass("Test");System.out.println(aClass.getClassLoader());}
}

运行结果(获取自定义类加载器):

需要注意的是加载的类名不能以java.开头,源码解析:

    private ProtectionDomain preDefineClass(String var1, ProtectionDomain var2) {if (!this.checkName(var1)) {throw new NoClassDefFoundError("IllegalName: " + var1);} else if (var1 != null && var1.startsWith("java.")) {throw new SecurityException("Prohibited package name: " + var1.substring(0, var1.lastIndexOf(46)));} else {if (var2 == null) {var2 = this.defaultDomain;}if (var1 != null) {this.checkCerts(var1, var2.getCodeSource());}return var2;}}

获取自定义类加载器的父类加载器:

public class BreakClassLoader extends ClassLoader {private String basePath;private final static String FILE_EXT = ".class";public void setBasePath(String basePath) {this.basePath = basePath;}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;}}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {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 {BreakClassLoader classLoader1 = new BreakClassLoader();
//        classLoader1.setBasePath("D:\\Test\\com\\rye\\");
//
//        Class<?> aClass = classLoader1.loadClass("Test");
//        System.out.println(aClass.getClassLoader());ClassLoader parent = classLoader1.getParent();System.out.println(parent);}
}

运行结果:

解析:

 ClassLoader类中提供了构造方法设置parent的内容(JDK8中):

    private ClassLoader(Void var1, ClassLoader var2) {this.classes = new Vector();this.defaultDomain = new ProtectionDomain(new CodeSource((URL)null, (Certificate[])null), (PermissionCollection)null, this, (Principal[])null);this.packages = new HashMap();this.nativeLibraries = new Vector();this.defaultAssertionStatus = false;this.packageAssertionStatus = null;this.classAssertionStatus = null;this.parent = var2;if (ClassLoader.ParallelLoaders.isRegistered(this.getClass())) {this.parallelLockMap = new ConcurrentHashMap();this.package2certs = new ConcurrentHashMap();this.domains = Collections.synchronizedSet(new HashSet());this.assertionLock = new Object();} else {this.parallelLockMap = null;this.package2certs = new Hashtable();this.domains = new HashSet();this.assertionLock = this;}}

这个构造方法由另外一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的是AppClassLoader。

    protected ClassLoader() {this(checkCreateClassLoader(), getSystemClassLoader());}

验证getSystemClassLoader方法返回的是AppClassLoader:

    public static void main(String[] args){System.out.println(getSystemClassLoader());}

运行结果:


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了打破双亲委派机制的方法、自定义类加载器等内容,希望对大家有所帮助。

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

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

相关文章

常用注解/代码解释(仅个人使用)

目录 第一章、代码解释①trim() 方法以及(Arrays.asList(str.split(reg)));②查询字典项②构建后端镜像shell命令解释 第二章、注解解释①PropertySource注解与Configurationproperties注解的区别 第三章、小知识①Linux系统中使用$符号表示变量 友情提醒: 先看文章目录&#…

C++标准学习--decltype

decltype / auto 是具有类型推导功能的 类型 描述/占位 符 decltype: 获取对象或表达式的类型auto: 类型自动推导 decltype 可以获取变量类型&#xff0c; &#xff08;并不同于python的type&#xff0c;但python能打印出type获取的名称&#xff0c; C通过typeid实现&#xff…

echarts -- 柱状图之柱状条如何显示白色侧阴影且鼠标移入时高亮

有个图表是要求柱状条的右下侧显示一个白色的侧阴影&#xff0c;一直没找到合适的方法&#xff0c; 加border或者shadowColor都达不到需求的效果。 因为柱状图 中series里可以包含多组数据&#xff0c;有几组就代表一个系列中有几个数据。这就代表series里要写七组数据。 对于上…

研发型企业怎样选择安全便捷的数据摆渡解决方案?

研发型企业在市场经济发展中发挥着至关重要的作用&#xff0c;研发型企业是指以科技创新为核心&#xff0c;以研发新产品、新技术、新工艺为主要业务的企业。这类企业注重技术创新和研发&#xff0c;持续不断地进行技术创新和产品升级&#xff0c;为经济发展注入新鲜的活力。 研…

iOS 应用上架指南:资料填写及提交审核

摘要 本文提供了iOS新站上架资料填写及提交审核的详细指南&#xff0c;包括创建应用、资料填写-综合、资料填写-IOS App和提交审核等步骤。通过本指南&#xff0c;您将了解到如何填写正确的资料&#xff0c;并顺利通过苹果公司的审核。 引言 在开发iOS应用后&#xff0c;将其…

Camunda SendTask和ReceiveTask

Activiti也有ReceiveTask&#xff0c;作用是进入该节点将自动挂起流程实例&#xff0c;直到被显式的唤醒。Activiti有MailTask是专门发送邮件的。 Camunda同时有SendTask和ReceiveTask&#xff0c;一般成对出现&#xff0c;感觉是将Activiti中的ReceiveTask拆成2个步骤&#x…

一级倒立摆控制 - 非线性 MPC 控制及 MATLAB 实现

系列文章目录 前言 本示例使用非线性模型预测控制器对象和块实现对小车上倒立摆的摆动和平衡控制。 本示例需要 Optimization Toolbox™ 软件为非线性 MPC 提供默认的非线性编程求解器&#xff0c;以计算每个控制间隔的最优控制动作。 一、摆锤/小车装配 本例中的被控对象是…

Tomcat解压打包文件和并部署

一、文件压缩和上传解压 1.本地打包好dist.tar.gz文件 2.通过xftp拖拽上传到知道文件夹下,或者通过命令: cp dist.tar.gz /path/to/destination/folder注:将dist.tar.gz复制到 /path/to/destination/folder文件夹下,该文件夹只是举个例子怎么复制和解压! 3.进入/path/…

k8s部署mongodb-sharded7.X集群(多副本集)

#mongodb-sharded 7.X版本CHART NAME: mongodb-sharded CHART VERSION: 7.0.5 APP VERSION: 7.0.2helm repo add bitnami https://charts.bitnami.com/bitnami helm pull bitnami/bitnami/mongodb-sharded --untar默认副本数较多。我修改为33 搜索关键字replicaCount 修改 最后…

数组和函数实践:扫雷游戏玩法和棋盘初始化(1)

各位少年&#xff0c;大家好&#xff0c;我是博主那一脸阳光&#xff0c;我们学会了数组&#xff0c;exturn声明外部文件&#xff0c;static修饰静态变量&#xff0c;那么很显然&#xff0c;我们需要用到我们学习这些&#xff0c;实现一个扫雷游戏。 扫雷游戏介绍以及玩法 在地…

【Github-Action】GithubAction 环境下,如何将临时生成的文件推送至指定分支。

通过这篇文章你可以掌握如何将github action 环境下临时生成的文件推送至指定分支&#xff0c;并且可以打开利用github开放的api做各种强大或有趣的事情的视野和思路。 如果你对github-action感兴趣&#xff0c;还可以看这篇文章&#xff0c; 这篇文章教会你如何开发Github Act…

【JaveWeb教程】(18) MySQL数据库开发之 MySQL数据库设计-DDL 如何查询、创建、使用、删除数据库数据表 详细代码示例讲解

目录 2. 数据库设计-DDL2.1 项目开发流程2.2 数据库操作2.2.1 查询数据库2.2.2 创建数据库2.2.3 使用数据库2.2.4 删除数据库 2.3 图形化工具2.3.1 介绍2.3.2 安装2.3.3 使用2.2.3.1 连接数据库2.2.3.2 操作数据库 2.3 表操作2.3.1 创建2.3.1.1 语法2.3.1.2 约束2.3.1.3 数据类…