彻底理解JVM类加载机制

news2025/1/21 0:08:04

文章目录

  • 一、类加载器和双亲委派机制
    • 1.1、类加载器
    • 1.2、双亲委派机制
    • 1.3、自定义类加载器
    • 1.4、打破双亲委派机制
  • 二、类的加载


java代码的执行流程图片来源:图灵学院
  由上图可知,创建对象,执行其中的方法,在java层面,最重要的有获取类加载器以及加载类两部分

一、类加载器和双亲委派机制

1.1、类加载器

  在JVM中,类加载器分为:

  • 启动类加载器:最核心的类加载器,用于加载JRE的lib目录下的核心类库
  • 扩展类加载器:负责加载位于JRE的lib目录下的ext扩展目录中的JAR类包。
  • 应用程序类加载器:负责加载ClassPath路径下的类包。
  • 自定义类加载器:用于加载用户自定义路径的类包。

  sun.misc包下的Launcher类,会在构造方法中对类加载器进行初始化:
在这里插入图片描述  构造方法中的关键部分:
在这里插入图片描述  sun.misc.Launcher.AppClassLoader#getAppClassLoader(获取应用程序类加载器方法)的最底层,将参数中传入的扩展类加载器,赋值给成员变量。
在这里插入图片描述  ExtClassLoader扩展类加载器继承自ClassLoader
在这里插入图片描述

1.2、双亲委派机制

  双亲委派机制的核心是,向上委派,向下加载:
在这里插入图片描述  即当加载某个类时,会从最底层的类加载器开始,逐个向上查找目标类,如果加载过,就直接返回。如果查找到最上层依旧没有,则从最上层自身负责的类加载路径中查找并加载。

  • 自己写的一个User类,第一次运行,应用程序类加载器没有找到,向上查找扩展类、启动类加载器,依然没有找到。就从最顶层的启动类加载器开始,查看该类是否在自己的加载范围内。很显然自定义的类,不在启动类加载器的范围内,向下委派到扩展类加载器,发现依旧不在自身的范围内,就再次委派给应用程序类加载器,这次发现在自己的加载范围内,就会加载。
  • java.lang包下的String类,第一次运行,应用程序类加载器没有找到,向上查找扩展类、启动类加载器,依然没有找到。就从最顶层的启动类加载器开始,查看该类是否在自己的加载范围内。java.lang包下的String类属于核心类库,启动类加载器发现它在自己应该加载的范围内,就会进行加载。

  双亲委派机制在源码中的体现在于java.lang.ClassLoader#loadClass(java.lang.String, boolean)

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //首先查看当前的类加载器是否加载过目标(底层是c/c++实现的方法)
        Class<?> c = findLoadedClass(name);
        //当前的类加载器没有加载过
        if (c == null) {
            long t0 = System.nanoTime();
            try {
            	  //扩展类加载器进行查找
            	  //这里的parent属性是当前类加载器的父加载器
                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();
                //查看当前的类路径,判断是否应该是自己加载。
                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方法:
在这里插入图片描述  扩展类加载器发现自身没有加载过,去找启动类加载器(底层由c/c++执行)在这里插入图片描述  启动类加载器返回的c为空,在扩展类加载器进入if判断,尝试自己加载,但是自定义的类不在自己的加载范围内,将null值的c返回到应用程序类加载器:
在这里插入图片描述  应用程序类加载器得到扩展类加载器的返回为空,就尝试自己加载并且返回:在这里插入图片描述

1.3、自定义类加载器

  自定义类加载器,需要继承ClassLoader,并且重写其中的findClassloadClass方法:

public class MyClassLoaderTest1 {
    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);
                //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

    }

    public static void main(String args[]) throws Exception {
        //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录
        Class clazz = classLoader.loadClass("com.itbaima.jvm.User");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

  上面的自定义类加载器,目的是为了加载D盘test文件夹下的com.itbaima.jvm的User.class文件,假设目前的classpath下和D盘指定目录下都有该文件?
在这里插入图片描述
在这里插入图片描述  那么根据双亲委派机制,User类应该由应用程序类加载器,而非自定义类加载器加载。原因很简单,当User类被启动类加载器向下加载时,在应用程序类加载器这一层,发现属于自己的classpath范围内有这个类,就会进行加载。
在这里插入图片描述  把classpath下的User类删除,则由自定义类加载器进行加载:
在这里插入图片描述

1.4、打破双亲委派机制

  打破双亲委派机制的关键在于自定义实现loadClass方法。在1.3案例的基础上,继续重写loadClass方法:

@Override
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) {
            long t0 = System.nanoTime();
            // If still not found, then invoke findClass in order
            // to find the class.
            long t1 = System.nanoTime();
            //让父类去加载Object
            if (!name.startsWith("com.itbaima.jvm")) {
                c = this.getParent().loadClass(name);
            }else {
                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方法中,去除了向上委派的逻辑,而是由自身进行加载,为什么还要加上

if (!name.startsWith("com.itbaima.jvm")) {
    c = this.getParent().loadClass(name);
 }

的判断?原因在于,用当前的类加载器去加载User类没有问题,但是User类继承了java.lang包下的Object类,如果把判断条件去除:
在这里插入图片描述  如果把Object.class文件复制一份到指定的目录呢?
在这里插入图片描述  答案也是否定的,因为在类加载的过程中JVM还有自己的安全监测机制,是不会允许核心的类被自定义加载的。在这里插入图片描述

二、类的加载

  类的加载,通过loadClass方法,会经历验证准备解析初始化四个阶段:

  • 验证阶段:主要是校验class文件是否符合规范,以及对正确性进行校验,最经典的是cafe babe模数校验。
  • 准备阶段:给类的静态属性分配内存,并且赋初值(基本数据类型为默认值,引用类型为null)。
  • 解析阶段:将符号引用转变为直接引用符号引用简单可以理解为类中的方法名,变量名等。而直接引用,是符号引用真正指向的内存地址。
  • 初始化:在这一步才是给类的静态属性赋值,并且执行静态代码块。

  静态代码块的执行先于构造方法:
在这里插入图片描述


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

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

相关文章

第148场双周赛:循环数组中相邻元素的最大差值、将数组变相同的最小代价、最长特殊路径、所有安放棋子方案的曼哈顿距离

Q1、循环数组中相邻元素的最大差值 1、题目描述 给你一个 循环 数组 nums &#xff0c;请你找出相邻元素之间的 最大 绝对差值。 **注意&#xff1a;**一个循环数组中&#xff0c;第一个元素和最后一个元素是相邻的。 2、解题思路 这个问题的核心是遍历循环数组并计算相邻…

电脑换固态硬盘

参考&#xff1a; https://baijiahao.baidu.com/s?id1724377623311611247 一、根据尺寸和缺口可以分为以下几种&#xff1a; 1、M.2 NVME协议的固态 大部分笔记本是22x42MM和22x80MM nvme固态。 在京东直接搜&#xff1a; M.2 2242 M.2 2280 2、msata接口固态 3、NGFF M.…

opengrok_windows_环境搭建

软件列表 软件名下载地址用途JDKhttps://download.java.net/openjdk/jdk16/ri/openjdk-1636_windows-x64_bin.zipindex 使用java工具tomcathttps://dlcdn.apache.org/tomcat/tomcat-9/v9.0.98/bin/apache-tomcat-9.0.98-windows-x64.zipweb服务器opengrokhttps://github.com/o…

《offer 来了:Java 面试核心知识点精讲 -- 原理篇》

在 Java 面试的战场上,只知皮毛可不行,面试官们越来越看重对原理的理解。今天就给大家分享一本能让你在面试中脱颖而出的 “武林秘籍”——《offer 来了:Java 面试核心知识点精讲 -- 原理篇》。 本书详细介绍了Java架构师在BAT和移动互联网公司面试中常被问及的核心知识,内…

Linux之网络套接字

Linux之网络套接字 一.IP地址和端口号二.TCP和UDP协议2.1网络字节序 三.socket编程的常见API四.模拟实现UDP服务器和客户端五.模拟实现TCP服务器和客户端 一.IP地址和端口号 在了解了网络相关的基础知识之后我们知道了数据在计算机中传输的流程并且发现IP地址在其中占据了确定…

迈向 “全能管家” 之路:机器人距离终极蜕变还需几步?

【图片来源于网络&#xff0c;侵删】 这是2024年初Figure公司展示的人形机器人Figure 01&#xff0c;他可以通过观看人类的示范视频&#xff0c;在10小时内经过训练学会煮咖啡&#xff0c;并且这个过程是完全自主没有人为干涉的&#xff01; 【图片来源于网络&#xff0c;侵删】…

几何数据结构之四叉树与八叉树

几何数据结构之四叉树与八叉树 四叉树的定义四叉树深度的计算公式推导假设&#xff1a;计算过程&#xff1a;1. 划分空间&#xff1a;2. 节点容纳的最小距离&#xff1a;3. 解出深度&#xff1a;4. 考虑常数项&#xff1a; 总结&#xff1a; 八叉树 四叉树的定义 四叉树&#…

(一)相机标定——四大坐标系的介绍、对应转换、畸变原理以及OpenCV完整代码实战(C++版)

一、四大坐标系介绍 1&#xff0c;世界坐标系 从这个世界&#xff08;world&#xff09;的视角来看物体 世界坐标系是3D空间坐标&#xff0c;每个点的位置用 ( X w , Y w , Z w ) (X_w,Y_w,Z_w) (Xw​,Yw​,Zw​)表示 2&#xff0c;相机坐标系 相机本身具有一个坐标系&…

嵌入式知识点总结 C/C++ 专题提升(一)-关键字

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.C语言宏中"#“和"##"的用法 1.1.(#)字符串化操作符 1.2.(##)符号连接操作符 2.关键字volatile有什么含意?并举出三个不同的例子? 2.1.并行设备的硬件寄存…

重塑商业智能:大数据改变商业的十种方式

在过去几年间&#xff0c;大数据一直在改变许多公司的运营方式。大数据指的是大量且多样的数据集&#xff0c;当这些数据被妥善收集和分析时&#xff0c;人们能够从中获取有价值的洞察信息。随着大数据逐渐应用于中小型企业&#xff0c;它有望彻底变革企业运营模式。以下将介绍…

基于Spring Boot的车间调度管理系统

基于 Spring Boot 的车间调度管理系统 一、系统概述 基于 Spring Boot 的车间调度管理系统是一个为制造企业车间生产活动提供智能化调度和管理解决方案的软件系统。它利用 Spring Boot 框架的便捷性和高效性&#xff0c;整合车间内的人员、设备、物料、任务等资源&#xff0c…

Ubuntu 24.04 LTS 安装 tailscale 并访问 SMB共享文件夹

Ubuntu 24.04 LTS 安装 tailscale 安装 Tailscale 官方仓库 首先&#xff0c;确保系统包列表是最新的&#xff1a; sudo apt update接下来&#xff0c;安装 Tailscale 所需的仓库和密钥&#xff1a; curl -fsSL https://tailscale.com/install.sh | sh这会自动下载并安装 …

Ubuntu 22.04 TLS 忘记root密码,重启修改的解决办法

1.想办法进入这个界面&#xff0c;我这里是BIOS引导的是按Esc按一下就行&#xff0c;UEFI的貌似是按Shift不得而知&#xff0c;没操作过。下移到Advanced options for Ubuntu&#xff0c;按enter 2.根据使用的内核版本&#xff0c;选择带「recovery mode」字样的内核版本&#…

故障诊断 | BWO白鲸算法优化KELM故障诊断(Matlab)

目录 效果一览文章概述BWO白鲸算法优化KELM故障诊断一、引言1.1、研究背景及意义1.2、故障诊断技术的现状1.3、研究目的与内容二、KELM基本理论2.1、KELM模型简介2.2、核函数的选择2.3、KELM在故障诊断中的应用三、BWO白鲸优化算法3.1、BWO算法基本原理3.2、BWO算法的特点3.3、…

TCP状态转移图详解

状态 描述 LISTEN represents waiting for a connection request from any remote TCP and port. SYN-SENT represents waiting for a matching connection request after having sent a connection request. SYN-RECEIVED represents waiting for a confirming connect…

LabVIEW 水电站厂内经济运行系统

基于 LabVIEW 的水电站经济运行系统&#xff0c;主要针对农村小水电站运行管理的不足进行改进&#xff0c;通过精确控制发电与用水量&#xff0c;最小化耗水量并优化负荷分配&#xff0c;提升水电站的运营效率和经济效益。 ​ LabVIEW 在系统中的功能特点 强大的图形化编程环…

蓝桥杯训练—矩形面积交

文章目录 一、题目二、示例三、解析四、代码 一、题目 平面上有两个矩形&#xff0c;它们的边平行于直角坐标系的X轴或Y轴&#xff0c;对于每个矩形&#xff0c;我们给出它的一对相对顶点的坐标&#xff0c;请你编程写出两个矩形的交的面积 输入格式&#xff1a; 输入包含两行…

Flask简介与安装以及实现一个糕点店的简单流程

目录 1. Flask简介 1.1 Flask的核心特点 1.2 Flask的基本结构 1.3 Flask的常见用法 1.3.1 创建Flask应用 1.3.2 路由和视图函数 1.3.3 动态URL参数 1.3.4 使用模板 1.4 Flask的优点 1.5 总结 2. Flask 环境创建 2.1 创建虚拟环境 2.2 激活虚拟环境 1.3 安装Flask…

基于机器学习的电信用户流失预测与数据分析可视化

完整源码项目包获取→点击文章末尾名片&#xff01; 背景描述 根据IBM商业社区分享团队描述&#xff0c;该数据集为某电信公司在加利福尼亚为7000余位用户&#xff08;个人/家庭&#xff09;提供电话和互联网服务的相关记录。描述用户基本情况&#xff0c;包括每位用户已注册的…

InVideo AI技术浅析(五):生成对抗网络

一、特效生成 1. 工作原理 特效生成是计算机视觉中的高级应用,旨在通过算法生成高质量的视觉特效,如风格迁移、图像到图像的翻译等。InVideo AI 使用生成对抗网络(GAN)来实现这一功能。GAN 通过生成器和判别器两个网络的对抗训练,生成逼真的视觉特效。 2. 关键技术模型…