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

JVM一之类加载子系统

JVM

整个JVM构成里面,由三部分组成:类加载系统、运行时数据区、执行引擎

Java虚拟机:各种硬件平台上的Java虚拟机实现

在这里插入图片描述

JVM架构图

在这里插入图片描述

类加载子系统

类加载的时机主要有以下4个

  1. 遇到new 、getstatic 、putstatic 和invokestatic 这四条指令时,如果对应的类没有初始化,则要对对应的类先进行初始化。
  2. 使用java.lang.reflect 包方法时,对类进行反射调用的时候。
  3. 初始化一个类的时候发现其父类还没初始化,要先初始化其父类
  4. 当虚拟机开始启动时,用户需要指定一个主类(main),虚拟机会先执行这个主类的初始化。

类加载的过程

  1. 全限定名称 ==> 二进制字节流加载class文件
  2. 字节流的静态数据结构 ==> 方法区的运行时数据结构
  3. 创建字节码Class对象

类的一生

在这里插入图片描述

字节码的加载途径

在这里插入图片描述

类加载器

JVM的类加载是通过ClassLoader及其子类来完成的。

在这里插入图片描述

检查顺序是自底向上:加载过程中会先检查类是否被已加载,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为加载此类,保证此类只所有ClassLoader加载一次。
加载的顺序是自顶向下:也就是由上层来逐层尝试加载此类。
启动类加载器(Bootstrap ClassLoader)
负责加载 JAVA_HOME\lib 目录中的,或通过**-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。由C++实现,不是ClassLoader的子类
扩展类加载器(Extension ClassLoader)
负责加载 JAVA_HOME\lib\ext 目录中的,或通过j
ava.ext.dirs**系统变量指定路径中的类库。

应用程序类加载器(Application ClassLoader):负责加载用户路径classpath上的类库
自定义类加载器(User ClassLoader)
作用:JVM自带的三个加载器只能加载指定路径下的类字节码,如果某些情况下,我们需要加载应用程序之外的类文件呢?就需要用到自定义类加载器,就像是在汽车行驶的时候,为汽车更换轮子。
比如本地D盘下的,或者去加载网络上的某个类文件,这种情况就可以使用自定义加载器了。

双亲委派模型与打破双亲委派

当一个类加载器收到类加载任务,会先交给其父类加载器去完成。因此,最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,子类才会尝试执行加载任务。 ----- 类都会优先由最顶级的类加载器加载(即自顶往下的方式加载类)

这里的双亲翻译的不准确的,应该是父类委派。

双亲委派模型是为了安全考虑,同时也避免了类多次加载。

// 双亲委派实现:java.lang.ClassLoader#loadClass(java.lang.String, boolean)
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1.首先, 检查class是否被加载,如果没有加载则进行加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                //2. 如果有父类,则交给父类加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //...
            }
            //3.父类没有加载到,则由子类进行加载
            if (c == null) {
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

为什么还需要破坏双亲委派?

  • 在实际应用中,双亲委派解决了Java 基础类统一加载的问题,但是却存在着缺陷。JDK中的基础类作为典型的api被用户调用,但是也存在api调用用户代码的情况,典型的如:SPI代码。这种情况就需要打破双亲委派模式。
  • 举个栗子:数据库驱动DriverManager。以Driver接口为例,Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,由系统类加载器加载。这个时候就需要启动类加载器来 委托 子类来加载Driver实现,这就破坏了双亲委派。类似情况还有很多

如何破坏双亲委派?

第一种方式

  • 在 jdk 1.2 之前,那时候还没有双亲委派模型,不过已经有了 ClassLoader 这个抽象类,所以已经有人继承这个抽象类,重写 loadClass 方法来实现用户自定义类加载器。

  • 而在 1.2 的时候要引入双亲委派模型,为了向前兼容, loadClass 这个方法还得保留着使之得以重写,新搞了个 findClass 方法让用户去重写,并呼吁大家不要重写 loadClass 只要重写 findClass。这就是第一次对双亲委派模型的破坏,因为双亲委派的逻辑在 loadClass 上,但是又允许重写loadClass,重写了之后就可以破坏委派逻辑了。

第二种方式:

​ SPI,如DriverManager

其源码如下:

static {
loadInitialDrivers();
}
private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>
                                                () {
                                                    public String run() {
                                                        return System.getProperty("jdbc.drivers");
                                                    }
                                                });
    } catch (Exception ex) {
        drivers = null;
    }
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            ServiceLoader<Driver> loadedDrivers =
                ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            }
            return null;
        }
    });
    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    for (String aDriver : driversList) {
        try {
            //在这里需要加载各个厂商实现的数据库驱动com.mysql.jdbc.Driver
            Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
        }
    }
}

在这里插入图片描述

第三种方式
为了满足热部署、不停机更新需求。OSGI 就是利用自定义的类加载器机制来完成模块化热部署,而它实现的类加载机制就没有完全遵循自下而上的委托,有很多平级之间的类加载器查找,具体就不展开了,有兴趣可以自行研究一下。


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

相关文章:

  • 多模态图文检索实战——基于CLIP实现图文检索系统(附源码)
  • AI驱动的可演化架构与前端开发效率
  • 【Java基础】正则表达式的使用与常用类分享
  • 业务日志设计
  • # Java 发送电子邮件示例
  • 『SQLite』常见函数的使用
  • `http_port_t
  • 『SQLite』常见日期时间函数的使用
  • java项目之旅游网站的设计与实现(源码+文档)
  • 网络分析与监控:阿里云拨测方案解密
  • ETCD渗透利用指南
  • 【Python】Flask报错:TimeoutError: QueuePool limit of size 10...以及日常bug处理
  • PyCharm简单调试
  • 【计算机组成原理课程设计】:实验0 ROM仿真、实验1 验证74L181运算和逻辑功能、实验2 运算器 2、实验 3 跑马灯、实验4 模拟微程序实现指令
  • swagger导出json
  • rabbitmq——岁月云实战笔记
  • C# async和await
  • Dexcap复现代码数据预处理全流程(四)——demo_clipping_3d.py
  • 东土科技参股广汽集团飞行汽车初创公司,为低空经济构建新型产业生态
  • 面向对象的思维hong