当前位置: 首页 > article >正文

JVM【Java虚拟机】基础知识(五)

1. 双亲委派机制

由于Java虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题。

💡双亲委派机制有什么用?

1.保证类加载的安全性

通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。

2.避免重复加载

双亲委派机制可以避免同一个类被多次加载。

双亲委派机制指的是:当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过,再由顶向下进行加载

在这里插入图片描述

☘每个类加载器都有一个父类加载器,在类加载的过程中,每个类加载器都会先检查是否已经加载了该类,如果已经加载则直接返回,否则会将加载请求委派给父类加载器。

在这里插入图片描述

☘如果所有的父类加载器都无法加载该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试加载。

在这里插入图片描述

第二次再去加载相同的类,仍然会向上进行委派,如果某个类加载器加载过就会直接返回。

在这里插入图片描述

向下委派加载起到了一个加载优先级的作用。

☘案例:

com.itheima.my.C这个类在当前程序的classpath中,看看是如何加载的。

首先自底向上一层一层的查找,发现都没有被加载过:

在这里插入图片描述

之后再自定向下开始加载,每一级类加载器检查该类是不是在它的加载路径中:

在这里插入图片描述

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

💡问题2:在自己的项目中去创建一个java.lang.String类,会被加载吗?

不能,会返回启动类加载器加载在rt.jar包中的String类

💡问题3:在Java中如何使用代码的方式去主动加载一个类呢?
方式1:使用Class.forName方法,使用当前类的类加载器去加载指定的类。
方式2:获取到类加载器,通过类加载器的loadClass方法指定某个类加载器加载。

//获取main方法所在类的类加载器,应用程序类加载器
ClassLoader classLoader = Demo1.class.getClassLoader();
//使用应用程序类加载器加载com.itheima.my.A
classLoader.loadClass("com.itheima.my.A");

☘细节:

每个Java实现的类加载器中保存了一个成员变量叫"父”(Parent)类加载器,可以理解为它的上级,并不是继承关系。

在这里插入图片描述

🐟注:启动类加载器是使用C++编写,没有父类加载器。

在Arthas中可以通过classloader -t 查看类加载器的父子关系。

☀面试题(高频)

类的双亲委派机制是什么?
1、当一个类加载器去加载某个类的时候,会自底向上向父类查找是否加载过,如果加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载。
2、应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。
3、双亲委派机制的好处有两点:第一是避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。第二是避免一个类重复地被加载。

2.打破双亲委派机制

打破双亲委派机制的三种方式:

  1. 自定义类加载器

    自定义类加载器并且重写loadC1ass方法,就可以将双亲委派机制的代码去除。Tomcat通过这种方式实现应用之间类隔离。

  2. 线程上下文类加载器

    利用上下文类加载器加载类,比如JDBC和JNDI等。

  3. Osgi框架的类加载器

    历史上Osgi框架实现了一套新的类加载机制,允许同级之间委托进行类的加载。

自定义加载器

​ 一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类,Tomcat要保证这两个类都能加载并且它们应该是不同的类。
​ 如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了。

如何解决?

Tomcat使用了自定义加载器来实现应用之间类的隔离,每一个应用会有一个独立的类加载器加载对应的类。

在这里插入图片描述

🐟先来分析ClassLoader的原理,ClassLoader中包含了4个核心方法。双亲委派机制的核心代码就位于loadClass方法中。

在这里插入图片描述

通过上面的分析可知,要想打破类的双亲委派机制,核心就是修改loadClass方法,改变其规则。

通过阅读双亲委派机制的核心代码,分析如何通过自定义的类加载器打破双亲委派机制。

打破双亲委派机制的核心就是将下边这一段代码重新实现。

//parent等于null说明父类加载器是启动类加载器,直接调用findBootstrapClassOrNull
//否则调用父类加载器的加载方法
if (parent != null){
 c=parent.loadClass(name,false);
}else{
 c=findBootstrapClassOrNull(name);
}
//父类加载器爱莫能助,我来加载!
if (c == null)
 c = findClass(name);

💧示例:

通过修改loadClass方法来自定义类加载器:

核心代码如下:

public class BreakClassLoader1 extends ClassLoader{
    //重写的loadClass方法
    @Override
    public 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);
    }
    //main方法
    public static void main(String[] args) throws ClassNotFoundException,InstantiationException,IllegalAccessException{
        BreakClassLoader1 classLoader1 = new BreakClassLoader1();
        classLoader1.setBasePath("D:\\lib\\");
        Class<?> clazz1 = classLoader1.loadClass("com.itheima.my.A");
        System.out.println(clazz1.getClassLoader());
        //自定义加载器的父类加载器
        ClassLoader parent = classLoader1.getParent();
        System.out.println(parent);//结果是应用程序类加载器
    }
}

💡思考:

在这里插入图片描述

以Jdk8为例,ClassLoader类中提供了构造方法设置parent的内容:

在这里插入图片描述

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

在这里插入图片描述

💡思考:

在这里插入图片描述

📕综上:

正确的去实现一个自定义的类加载器的方式是重写findClass方法,这样不会破坏双亲委派机制。

线程上下文类加载器

这里以JDBC为例:

JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql驱动、oracle驱动。

DriverManager类位于rt.jar包中,由启动类加载器加载。

依赖中的mysq驱动对应的类,由应用程序类加载器来加载。

📕因此,这里面产生了一个冲突:DriverManager属于rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制。

在这里插入图片描述

💡这里首先有一个问题:DriverManager怎么知道jar包中要加载的驱动在哪儿?

这里面需要引入一个机制:SPI机制

spi全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。

spi的工作原理:

1.在ClassPath路径下的META-lNF/services文件夹中,以接口的全限定名来命名文件名,对应的文件里面写该接口的实现。

2.使用ServiceLoader加载实现类。

在这里插入图片描述

DriverManage使用SPI机制,最终加载jar包中对应的驱动类。

💡SPI中是如何获取到应用程序类加载器的?
SPI中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器。

在这里插入图片描述

那问题又来了,是不是只要是创建一个线程就是应用类加载器进行加载呢?

public class NewThreadDemo{
    public static void main(String[] args){
        new Thread(new Runnable(){
            @Override
            public void run(){	
                System.out.println(Thread.currentThread().getContextclassLoader());
			}
		}).start();
    }
}
//结果:就是应用类加载器
//sun.misc.Launcher$AppClassLoader@18b4aac2

💧总结:JDBC案例中真的打破了双亲委派机制吗?实际上是没有打破的,细节上每个类都是按照双亲委派机制来查找类加载器的。

在这里插入图片描述

Osgi框架的类加载器(了解)

历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载。OSG还使用类加载器实现了热部署的功能。

在这里插入图片描述


http://www.kler.cn/a/453408.html

相关文章:

  • JS 异步 ( 一、异步概念、Web worker 基本使用 )
  • 分布式专题(10)之ShardingSphere分库分表实战指南
  • Go快速开发框架2.6.0版本更新内容快速了解
  • Java爬虫实战:深度解析VIP商品详情获取技术
  • 将Minio设置为Django的默认Storage(django-storages)
  • Yolo11改策略:卷积改进|SAC,提升模型对小目标和遮挡目标的检测性能|即插即用
  • ChatGPT与Postman协作完成接口测试(三)
  • 【人工智能】基于Python与Keras的图像风格迁移实现与解析
  • 典型常见的基于知识蒸馏的目标检测方法总结一
  • 每天40分玩转Django:Django部署概述
  • 用微软365邮箱收发邮件【azure-应用注册】
  • 如何通过HTTP API检索Doc
  • 3D坐标下,一点在某一线段上的左右方向的判定
  • 图论基础算法/DFS+BFS+Trie树
  • 【MyBatis 核心工作机制】注解式开发与动态代理原理
  • 君正buildroot2020在Ubuntu22编译报错
  • 【gopher的java学习笔记】spring web接口404了怎么办
  • go语言中的字符串详解
  • 论文分享—— 软件物料清单(SBOM)开源与专有工具的现状研究
  • uniapp 微信小程序 数据空白展示组件
  • 化妆造型门店小程序怎么做?你的造型魅力如何宣传?
  • 【基础篇】2. Jaspersoft Studio初探索 - 基于模板创建报表
  • HTML5实现好看的圣诞节网站源码
  • 数据之林的守护者:二叉搜索树的诗意旅程
  • DAY37|动态规划Part05|完全背包理论基础、LeetCode:518. 零钱兑换 II、377. 组合总和 Ⅳ、70. 爬楼梯 (进阶)
  • taiwindcss