彻底理解JVM类加载机制

文章目录

  • 一、类加载器和双亲委派机制
    • 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)。
  • 解析阶段:将符号引用转变为直接引用符号引用简单可以理解为类中的方法名,变量名等。而直接引用,是符号引用真正指向的内存地址。
  • 初始化:在这一步才是给类的静态属性赋值,并且执行静态代码块。

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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【2024年华为OD机试】 (A卷,200分)- 快递投放问题(Java JS PythonC/C++)

一、问题描述 题目解析 题目描述 有 N 个快递站点用字符串标识&#xff0c;某些站点之间有道路连接。每个站点有一些包裹要运输&#xff0c;每个站点间的包裹不重复。路上有检查站会导致部分货物无法通行&#xff0c;计算哪些货物无法正常投递。 输入描述 第一行输入 M N&…

python爬虫爬取淘宝商品比价||淘宝商品详情API接口

最近在学习北京理工大学的爬虫课程&#xff0c;其中一个实例是讲如何爬取淘宝商品信息&#xff0c;现整理如下&#xff1a; 功能描述&#xff1a;获取淘宝搜索页面的信息&#xff0c;提取其中的商品名称和价格 探讨&#xff1a;淘宝的搜索接口 翻页的处理 技术路线:requests…

【大数据】机器学习------支持向量机(SVM)

支持向量机的基本概念和数学公式&#xff1a; 1. 线性可分的支持向量机 对于线性可分的数据集 &#xff0c;其中(x_i \in R^d) 是特征向量 是类别标签&#xff0c;目标是找到一个超平面 &#xff0c;使得对于所有 的样本 &#xff0c;对于所有(y_i -1) 的样本&#xff0c;…

服务器数据恢复—EMC存储POOL中数据卷被删除的数据恢复案例

服务器数据恢复环境&故障&#xff1a; EMC Unity 400存储连接了2台硬盘柜。2台硬盘柜上一共有21块硬盘&#xff08;520字节&#xff09;。21块盘组建了2组RAID6&#xff1a;一组有11块硬盘&#xff0c;一组有10块硬盘。 在存储运行过程中&#xff0c;管理员误操作删除了 2组…

Ubuntu系统备份与还原

Ubuntu系统备份与还原 前言ClonezillaTimeshift安装图形界面使用命令行使用 前言 Linux系统备份软件有Clonezilla和TimeShift。 使用Clonezilla需要准备USB启动盘&#xff0c;而Timeshift不需要。 因此推荐使用Timeshift进行备份与还原。 Clonezilla 官网&#xff1a;https:…

CSS:语法、样式表、选择器

目录 一、语法 二、创建 外部样式表 内部样式表 内联样式 三、选择器 ID选择器 类选择器 伪类选择器 :hover a:link a:active a:visited 属性选择器 伪元素选择器 ::first-letter ::first-line ::selection ::placeholder ::before 和::after 通配选择器 标…

记录一次 centos 启动失败

文章目录 现场1分析1现场2分析2搜索实际解决过程 现场1 一次断电,导致 之前能正常启动的centos 7.7 起不来了有部分log , 关键信息如下 [1.332724] XFS(sda3): Internal error xfs ... at line xxx of fs/xfs/xfs_trans.c [1.332724] XFS(sda3): Corruption of in-memory data…

YOLOv5训练长方形图像详解

文章目录 YOLOv5训练长方形图像详解一、引言二、数据集准备1、创建文件夹结构2、标注图像3、生成标注文件 三、配置文件1、创建数据集配置文件2、选择模型配置文件 四、训练模型1、修改训练参数2、开始训练 五、使用示例1、测试模型2、评估模型 六、总结 YOLOv5训练长方形图像详…

“AI智能防控识别系统:守护安全的“智慧卫士”

在如今这个科技飞速发展的时代&#xff0c;安全问题始终是大家关注的焦点。无论是企业园区、学校校园&#xff0c;还是居民社区&#xff0c;都希望能有一双“慧眼”时刻守护着&#xff0c;及时发现并防范各种安全隐患。而AI智能防控识别系统&#xff0c;就像一位不知疲倦、精准…

使用FFmpeg和Python将短视频转换为GIF的使用指南

使用FFmpeg和Python将短视频转换为GIF的使用指南 在数字时代&#xff0c;GIF动图已成为表达情感和分享幽默的重要媒介。无论是社交媒体上的搞笑片段还是创意项目中的视觉效果&#xff0c;GIF都能迅速抓住观众的注意力。然而&#xff0c;很多人不知道如何将短视频转换为GIF。本…

黑马Java面试教程_P1_导学与准备篇

系列博客目录 文章目录 系列博客目录导学Why?举例 准备篇企业是如何筛选简历的(筛选简历的规则)HR如何筛选简历部门负责人筛选简历 简历注意事项简历整体结构个人技能该如何描述项目该如何描述 应届生该如何找到合适的练手项目项目来源找到项目后&#xff0c;如何深入学习项目…

【前端动效】HTML + CSS 实现打字机效果

目录 1. 效果展示 2. 思路分析 2.1 难点 2.2 实现思路 3. 代码实现 3.1 html部分 3.2 css部分 3.3 完整代码 4. 总结 1. 效果展示 如图所示&#xff0c;这次带来的是一个有趣的“擦除”效果&#xff0c;也可以叫做打字机效果&#xff0c;其中一段文本从左到右逐渐从…

C#学习笔记 --- 基础补充

1.operator 运算符重载&#xff1a;使自定义类可以当做操作数一样进行使用。规则自己定。 2.partial 分部类&#xff1a; 同名方法写在不同位置&#xff0c;可以当成一个类使用。 3.索引器&#xff1a;使自定义类可以像数组一样通过索引值 访问到对应的数据。 4.params 数…

「刘一哥GIS」系列专栏《GRASS GIS零基础入门实验教程(配套案例数据)》专栏上线了

「刘一哥GIS」系列专栏《GRASS GIS零基础入门实验教程》全新上线了&#xff0c;欢迎广大GISer朋友关注&#xff0c;一起探索GIS奥秘&#xff0c;分享GIS价值&#xff01; 本专栏以实战案例的形式&#xff0c;深入浅出地介绍了GRASS GIS的基本使用方法&#xff0c;用一个个实例讲…

当当网书籍信息爬虫

1.基本理论 1.1概念体系 网络爬虫又称网络蜘蛛、网络蚂蚁、网络机器人等&#xff0c;可以按照我们设置的规则自动化爬取网络上的信息&#xff0c;这些规则被称为爬虫算法。是一种自动化程序&#xff0c;用于从互联网上抓取数据。爬虫通过模拟浏览器的行为&#xff0c;访问网页…

【12】Word:张老师学术论文❗

目录 题目 ​NO2 NO3 NO4 NO5 NO6 NO7.8 题目 NO2 布局→页面设置→纸张&#xff1a;A4→页边距&#xff1a;上下左右边距→文档网格&#xff1a;只指定行网格→版式&#xff1a;页眉和页脚&#xff1a;页脚距边界&#xff1a;1.4cm居中设置论文页码&#xff1a;插入…

RabbitMQ实现延迟消息发送——实战篇

在项目中&#xff0c;我们经常需要使用消息队列来实现延迟任务&#xff0c;本篇文章就向各位介绍使用RabbitMQ如何实现延迟消息发送&#xff0c;由于是实战篇&#xff0c;所以不会讲太多理论的知识&#xff0c;还不太理解的可以先看看MQ的延迟消息的一个实现原理再来看这篇文章…

【PCL】Segmentation 模块—— 欧几里得聚类提取(Euclidean Cluster Extraction)

1、简介 PCL 的 Euclidean Cluster Extraction&#xff08;欧几里得聚类提取&#xff09; 是一种基于欧几里得距离的点云聚类算法。它的目标是将点云数据分割成多个独立的簇&#xff08;clusters&#xff09;&#xff0c;每个簇代表一个独立的物体或结构。该算法通过计算点与点…

ElasticSearch上

安装ElasticSearch Lucene&#xff1a;Java语言的搜索引擎类库&#xff0c;易扩展&#xff1b;高性能&#xff08;基于倒排索引&#xff09;Elasticsearch基于Lucene&#xff0c;支持分布式&#xff0c;可水平扩展&#xff1b;提供Restful接口&#xff0c;可被任何语言调用Ela…

GitLab:添加SSH密钥之前,您不能通过SSH来拉取或推送项目代码

1、查看服务器是否配置过 [rootkingbal-ecs-7612 ~]# cd .ssh/ [rootkingbal-ecs-7612 .ssh]# ls authorized_keys id_ed25519 id_ed25519.pub id_rsa id_rsa.pub2、创建密钥 $ ssh-keygen -t rsa -C kingbalkingbal.com # -C 后写你的邮箱 一路回车 3、复制密钥 [rootk…