JVM类加载和双亲委派机制

news2024/10/12 18:15:50

当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把类加载到JVM,本文主要说明类加载机制和其具体实现双亲委派模式。

一、类加载机制

类加载过程

类加载的过程是将类的字节码加载到内存中的过程,主要包括:加载-->链接-->初始化,其中链接还包括验证、准备、解析3个步骤。

  1. 加载:将class文件加载到内存,在方法区生成运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应Class实例的引用,生成的Class实例对象放到堆中;
  2. 验证:验证这个class文件是否合法,包括文件格式的校验,元数据类型的校验等;
  3. 准备:为类变量分配内存空间,但此时只是初始化为默认值而非真实值,但对于final变量此时会初始化为真实值;
  4. 解析:将符号引用(相对引用)转换为直接引用,符号引用是class文件的相对表达方式,直接引用就是在该系统里地址指针,比如hello()方法为符号引用,0x12345678为直接引用;
  5. 初始化:初始化类变量成真实值,初始化静态代码段,实际执行的是类构造器 方法。

image-20230909143853603

类加载过程是懒加载的策略,只有当该类被使用了才会被初始化,实际就是调用classLoader的 方法执行的过程;会触发类的初始化操作条件为:(1)需要创建新的对象,执行了new操作;(2)调用了类的静态变量或静态方法;(3)通过反射机制来获取某个类的时候;

利用new实例化对象的过程

Java的对象实例化的过程是调用 方法,在进行new操作的时候会执行实例化操作,实例化的过程主要 是调用构造方法的过程。在进行对象实例化前,已经初始化静态变量和静态代码段,实例化过程会初始化变量和代码块、调用构造方法进行实例化,利用new操作实例化对象的过程:

二、双亲委派

类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器:

  • 引导类加载器Bootstrap ClassLoader:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等;
  • 扩展类加载器Extension ClassLoader:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包;
  • 应用程序类加载器Application ClassLoader:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类;
  • 自定义加载器Customer ClassLoader:负责加载用户自定义路径下的类包;
image-20230909144438391

以下代码打印出各个类关联的ClassLoader情况:

public class TestJDKClassLoader {

    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());

        System.out.println();
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassloader = appClassLoader.getParent();
        ClassLoader bootstrapLoader = extClassloader.getParent();
        System.out.println("the bootstrapLoader : " + bootstrapLoader);
        System.out.println("the extClassloader : " + extClassloader);
        System.out.println("the appClassLoader : " + appClassLoader);

        System.out.println();
        System.out.println("bootstrapLoader加载以下文件:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i]);
        }

        System.out.println();
        System.out.println("extClassloader加载以下文件:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        System.out.println("appClassLoader加载以下文件:");
        System.out.println(System.getProperty("java.class.path"));

    }
}

类加载的具体实现是通过双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。

简单理解就是:能父加载器做的事就父加载器做,父加载器做不了的事情才自己来做

设计双亲委派机制的好处是:

  1. 保障类的唯一性:ClassLoader的双亲委派模型保障一个类在类加载器的唯一性,父类已经加载了该类,子类就不再加载,保障被加载类的唯一性。
  2. 实现沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改。

类加载器实现原理

先说明类加载器本身初始化的逻辑:

  1. 在sun.misc.Launcher类中创建JVM启动器实例。
  2. 在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。
  3. JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。
//Launcher的构造方法

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
      // 构造扩展类加载器,在构造的过程中将其父加载器设置为null
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
         //构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
        //Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
        //设置当前线程的类加载器为应用类加载器
    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    //...
}

再说明一下AppClassLoader的loadClass方法,该方法是进行class文件加载的方法,但最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
//ClassLoader的loadClass方法,里面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 检查当前类加载器是否已经加载了该类
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {  //如果当前加载器父加载器不为空则委托父加载器加载该类
                    c = parent.loadClass(name, false);
                } else {  //如果当前加载器父加载器为空则委托引导类加载器加载该类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                //都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {  //不会执行
            resolveClass(c);
        }
        return c;
    }
}

用户可以设置自定义类加载器来打破默认的双亲委派模式,主要是实现loadClass()方法,在这个方法中可以自定义加载class文件的逻辑。

public class MyClassLoaderTest {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                    + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;

        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        /**
         * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
         * @param name
         * @param resolve
         * @return
         * @throws ClassNotFoundException
         */
        protected Class<?> loadClass(String name, boolean resolve)
                throws ClassNotFoundException {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    }

    public static void main(String args[]) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("xxx");
        //尝试用自己改写类加载机制去加载自己写的java.lang.String.class
        Class clazz = classLoader.loadClass("java.lang.String");
        Object obj = clazz.newInstance();
        Method method= clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

打破双亲委托的方法:

通过线程上下文类加载器Thread Context ClassLoader#setContextClassLoader(ClassLoader c)可以指定当前线程的类加载器,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。

本文由博客一文多发平台 OpenWrite 发布!

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

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

相关文章

基于SSM的物流管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Python判断多个文件夹的文件夹名是否包含“分公司”或“营销中心”怎么处理?(方法一)...

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 晓畅军事&#xff0c;试用于昔日。 大家好&#xff0c;我是皮皮。 一、前言 前几天在Python最强王者群【哎呦喂 是豆子&#xff5e;】问了一个Python自…

ASUS华硕天选4笔记本FX507VU FX707V原厂Win11系统

自带网卡、显卡、声卡等所有驱动、出厂主题壁纸LOGO、Office办公软件、华硕电脑管家、奥创控制中心等预装程序 链接&#xff1a;https://pan.baidu.com/s/1KwFu-39x7UJsFKN4BQMoBQ?pwdf5xa 提取码&#xff1a;f5xa

基于Java+SpringBoot+Vue前后端分离库存管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

《Graph of Thoughts: Solving Elaborate Problems with Large Language Models》中文翻译

《Graph of Thoughts: Solving Elaborate Problems with Large Language Models》- 思维图&#xff1a;用大型语言模型解决复杂的问题 论文信息摘要1. 介绍2. 背景及符号2.1 语言模型和情境学习 3. GoT 框架3.1 推理过程3.2 思想转变3.3 评分和排名思路 4. 系统架构及扩展性4.1…

深圳汇报片制作需要优先了解哪些信息

在现代社会中&#xff0c;汇报片成为了一种重要的传媒形式&#xff0c;被广泛应用于各个领域。无论是在企业、政府机构还是学术界&#xff0c;制作一部高质量的汇报片都是非常重要的。而要制作出一部成功的汇报片&#xff0c;一个合理的制作结构是至关重要的。 一个典型的汇报…

数据结构:线性表之-单向链表(无头)

目录 什么是单向链表 顺序表和链表的区别和联系 顺序表&#xff1a; 链表&#xff1a; 链表表示(单项)和实现 1.1 链表的概念及结构 1.2单链表(无头)的实现 所用文件 将有以下功能&#xff1a; 链表定义 创建新链表元素 尾插 头插 尾删 头删 查找-给一个节点的…

H.264编码及AAC编码基础

文章目录 前言一、视频编码的实现原理1、视频编码技术的基本原理2、视频编码技术的实现方法3、运动估计和补偿①、块&#xff08;Block&#xff09;与宏块&#xff08;MicroBlock&#xff09;②、I 帧、P 帧、B 帧的小结③、I 帧&#xff08;帧内编码&#xff09;④、如何衡量和…

软件测试中的43个功能测试点总结

功能测试就是对产品的各功能进行验证&#xff0c;根据功能测试用例&#xff0c;逐项测试&#xff0c;检查产品是否达到用户要求的功能。针对web系统的常用测试方法如下&#xff1a; 1、页面链接检查&#xff1a; 每一个链接是否都有对应的页面&#xff0c;并且页面之间切换正…

EasyPHP-Devserver-17安装和配置mantisBT

文章目录 1、准备工作2、安装easyphp2.1 http://127.0.0.1 无法访问 3、安装mantisBT和phpMyAdmin3.1 配置浏览器的访问url和端口号&#xff08;配置局域网内可访问&#xff09;3.2 安装mantis 4、Administrator 注册新用户时设置登录密码5、附件上传6、邮件配置 文章参考自&am…

模型推理后处理C++代码优化案例

文章目录 项目场景&#xff1a;问题描述原因分析&#xff1a;解决方案&#xff1a;小结 项目场景&#xff1a; 经过推理的后处理运行时间的优化。 先来看下优化前后的时间对比&#xff1a; 优化前&#xff1a; 优化后&#xff1a; 提升还是很大的。 问题描述 模型推理后得…

mybatis-plus 数据字段进行加解密入库,且加密字段支持模糊搜索

mybatis-plus 数据进行字段加解密入库&#xff0c;加密字段支持模糊搜索 前提介绍 &#xff08;开发环境需求&#xff09; 1. 开发框架、环境 springbootmybatis-plusmysql5.7&#xff08;oracle应该也是可以的&#xff0c;没有测试&#xff0c;但实现思路是都可以满足&…

CSP 202206-1 归一化处理

答题 #include<iostream> #include<cmath> using namespace std;int main() {int n;double variance0,average0;cin>>n;double a[n];for(int i0;i<n;i){cin>>a[i];averagea[i];}averageaverage/n;for(int i0;i<n;i){variance(a[i]-average)*(a[…

Redis事务的理解

介绍 Redis通过MULTI、EXEC、WATCH等命令来实现事务功能。 事务提供了一种将多个命令请求打包&#xff0c;然后一次性、按照顺序地执行多个命令的机制&#xff0c;并且在事务执行期间&#xff0c;服务器不会因为其他客户端请求而中断事务的执行功能&#xff0c;他会将事务中的…

nvidia-smi 命令详解

nvidia-smi 命令详解 1. nvidia-smi 面板解析2. 显存与GPU的区别 Reference: nvidia-smi命令详解 相关文章&#xff1a; nvidia-smi nvcc -V 及 CUDA、cuDNN 安装 nvidia-smi(NVIDIA System Management Interface) 是一种命令行实用程序&#xff0c;用于监控和管理 NVIDIA G…

Jetsonnano B01 笔记3:GPIO上拉下拉-输入输出读取

今日继续我的jetsonnano学习之路&#xff0c;今日学习的是GPIO的上拉下拉&#xff0c;输入输出的读取&#xff0c;文章贴出完整操作步骤过程&#xff0c;贴出源码。 目录 Linux常用文件命令&#xff1a; ls&#xff08;list&#xff09;列表&#xff1a; man&#xff1a; …

系统报错“由于找不到msvcp140.dll无法继续执行代码”的处理方法

我在使用电脑时&#xff0c;突然发现了一个错误提示&#xff1a;“无法启动程序&#xff0c;因为找不到msvcp140.dll文件”。这让我非常困惑&#xff0c;因为我确定这个文件应该存在于我的电脑上。但是电脑依然报错“由于找不到msvcp140.dll无法继续执行代码”&#xff0c;这个…

apache-activemq-5.17.1一键安装安装

下载 安装 双击InstallService.bat脚本 查看是否安装完成

C# Solidworks二次开发:创建草图文本和创建草图中心线API详解

今天要介绍的是关于如何创建草图文本的API以及创建草图中心线的API&#xff0c;同时把一些连带的API也会和大家介绍一下&#xff0c;依然是满满的干货。 &#xff08;1&#xff09;创建草图文本API&#xff0c;InsertSketchText() 这个API的输入参数如下图所示&#xff1a; 一…

SD、SDIO和MMC接口基础和规范介绍

在MMC规范发展的过程中出现了很多的名词&#xff0c;如SDHC、SDIO、SDXC等&#xff0c;每次看到这些不同的规范都有点懵&#xff0c;也很容易搞混&#xff0c;所以本篇文章就来介绍一下MMC规范发展过程中出现的一些新的规范&#xff0c;并详细地理解一下SD和SDIO。 文章目录 1 …