JVM-类的加载机制

news2025/1/10 21:39:40

目录

  • 一、类的生命周期
  • 二、类加载的过程
  • 三、类加载的时机
  • 四、类加载器
  • 五、双亲委派模型
  • 六、自定义类加载器


一、类的生命周期

当编写完一个 java 类之后,经过编译就能够得到一个 .class(字节码)文件,这种字节码文件需要在 JVM 中运行。

在这里插入图片描述

java 类的生命周期是指一个 .class 文件从被加载到虚拟机内存中开始,到卸载出内存结束的全过程。一个类完整的生命周期会经历 加载、连接、初始化、使用和卸载五个阶段。其中连接又包含了验证、准备、解析这三个部分。加载、验证、准备、初始化、卸载这 5 个阶段的顺序是确定的。解析阶段不一定,它在某些情况下可以初始化阶段之后在开始,这是为了支持 Java 语言的运行时绑定。

在这里插入图片描述


二、类加载的过程

当程序要使用某个类时,如果类还未被加载到内存中,则系统会通过类的加载、类的连接、类的初始化这三个步骤进行初始化,详细点说就是加载、验证、准备、解析、初始化这五大部分。如果不出意外情况,JVM 将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化

  • 加载:根据路径找到相应的 .class 文件然后通过 IO 写入 JVM 的方法区中,同时在堆中创建一个 java.lang.Class 对象
  • 验证:校验加载到的 .class 文件是否正确
  • 准备:给类中的静态变量分配内存空间,将其初始化为默认值。此阶段只为静态变量(即 static 修饰的字段变量)分配内存,并且设置该变量的初始值(比如:static int num = 5; 在这一阶段的时候 num 会被初始化为 0 而不是 5),对于 static final 修饰的变量,在编译的时候就会分配,也不会分配实例变量的内存
  • 解析: 将类的二进制数据中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,直接引用直接指向内存中的地址
  • 初始化:对类的静态变量初始化为指定的值,执行静态代码块

类加载底层详细流程:

在这里插入图片描述


三、类加载的时机

虚拟机规范中并没有强制约束何时进行加载,但以下几种情况下必须要对类进行加载(加载-初始化):

  • 使用 new 关键字实例化对象
  • 读取或者设置一个类的静态变量的时候
  • 调用类对应的静态方法的时候
  • 对类进行反射调用的时候
  • 初始化子类时,父类会先被初始化
  • 虚拟机启动时,定义了main()方法的那个类先初始化

以上几种场景的行为称为对一个类的主动引用,除此之外,所有引用类的方式都不会触发初始化,称为被动引用,常见的被动引用有:

  • 通过子类引用父类的静态字段,不会导致子类初始化
System.out.println(SubClass.value);  // value 字段在 SuperClass 中定义
  • 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法
SuperClass[] sca = new SuperClass[10];
  • 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
System.out.println(ConstClass.HELLOWORLD);

四、类加载器

类加载器的作用就是将 class 文件加载到内存中,并为之生成对应的 java.lang.Class 对象。在 JDK 中主要有三种类加载器,它们分别是引导类加载器扩展类加载器应用类加载器。同样,我们可以通过继承 java.lang.ClassLoader自定义类加载器

类加载器的层级关系:

在这里插入图片描述

  • 启动类加载器(bootstrap ClassLoader)
    • 它负责加载 Java 的核心类库((JAVA_HOME/jre/lib/rt.jar、resource.jar或sun.boot.class.path路径下的内容) ,用于提供JVM自身需要的类。
  • 扩展类加载器(extension ClassLoader)
    • 从java.ext.dirs系统属性所指定的目录中加载类库,或从 JDK 的安装目录的 jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载
  • 应用程序加载器(application ClassLoader)
    • 负责加载 ClassPath 路径下的 .class 字节码文件,主要是用于加载程序员自己写的类
  • 自定义加载器
    • 负责加载程序员定义路径下的 class 字节码文件

类加载器之间是继承关系吗?

在这里插入图片描述

不是,从上面可以看到 ExtClassLoaderAppClassLoader 这两个加载器都是 Launcher 的内部类,而且全部继承于 URLClassLoader,而 URLClassLoader 最后又继承于 ClassLoader

在这里插入图片描述
ClassLoader 中有一个 parent 属性记录了它们之间的关系

在这里插入图片描述

所以类加载器的体系不是继承,而是委派体系。

我们可以通过以下代码证实类的加载器之间的关系:

public class LoaderDemo {
    public static void main(String[] args) {

        // 获取系统默认的类加载器:应用类加载器
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(appClassLoader);

        // 获取应用类加载器的父类加载器:扩展类加载器
        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println(extClassLoader);

        // 获取扩展类加载器的父类加载器:引导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);
    }
}

在这里插入图片描述

因为 bootstrapClassLoader 在 JVM 中,而 JVM 又是 C++ 编写的,在 Java 环境中是获取不到该类的,所以会显示为 null


如何获取类的加载器?

如果想获取加载某个类的类加载器,可以通过类的字节码对象(XXX.class.getClassLoader() 方法获取

例如:

public class LoaderDemo {
    public static void main(String[] args) {

        System.out.println(String.class.getClassLoader());
        System.out.println(DNSNameService.class.getClassLoader().getClass().getName());
        System.out.println(LoaderDemo.class.getClassLoader().getClass().getName());

    }
}

在这里插入图片描述


五、双亲委派模型

在这里插入图片描述
双亲委派模型是指当调用类加载器的 loadClass 方法进行类加载时,该类加载器会首先请求它的父类加载器进行加载,依次递归。如果所有父类加载器都加载失败,则当前类加载器自己进行加载操作。

简单来说就是自底向上检查是否加载成功,自顶向下尝试加载。

我们可以通过 ClassLoader 类的 loadClass 方法了解到双亲委派的实现,源码如下:

    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();
                try {
                	// 检查该类的父级加载器是否不为 null
                    if (parent != null) {
                    	// 调父级加载器的 loadClass 方法
                    	// 假如当前类加载器为 AppClassLoader,那么它就会去调 ExtClassLoader 的 loadClass 方法,而 AppClassLoader 和 ExtClassLoader 实际上都是调用了 ClassLoader 中的 loadClass 方法
                        c = parent.loadClass(name, false);
                    } else {
                    	// 这里就会到 C++ 写的类加载器来加载类
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                	// 如果 c 为 null,则表明 bootstrap 类加载器也没有加载成功
                    // 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;
        }
    }


    /**
     * Returns a class loaded by the bootstrap class loader;
     * or return null if not found.
     */
    private Class<?> findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

URLClassLoader 类的 findClass 方法源码:

    protected Class<?> findClass(final String name)
         throws ClassNotFoundException
    {
        try {
            return AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            throw new ClassNotFoundException(name);
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
    }

为什么要用双亲委派模型?

  1. Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父类加载器已经加载了该类时,就没有必要再 ClassLoader 再加载一次。
  2. 考虑到安全因素,java 核心 API 中定义类型不会被随意替换,这样便可以防止核心 API 库被随意篡改。
  3. 保证类的唯一性

双亲委派模型能否被打破?

可以,双亲委派模型不是一个强制性的约束模型,而是一个建议型的类加载器实现方式。如果想要打破这种模型,就需要自定义一个类加载器,重写其中的 loadClass 方法,使其不进行双亲委派即可。


什么情况下需要打破这种双亲委派模型?

JDBC 中在核心类库 rt.jar 的加载过程中需要加载第三方厂商的类(比如常用的数据库),直接指定使用应用程序类加载器来加载这些类。就需要打破双亲委派模型。

Tomcat 中的 web 容器类加载器也破坏了双亲委托模式的,自定义的 WebApplicationClassLoader除入了核心类库外,都是优先加载自己路径下的Class这样有利于隔离安全热部署。

六、自定义类加载器

自定义加载器需要:

  • 继承 ClassLoader
  • 覆盖 findClass() 方法或者 loadClass() 方法
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {

    /**
     * 如果重写 loadClass 方法则会打破双亲委派
     */
//    @Override
//    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//        // todo ...
//        return null;
//    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
            InputStream inputStream = getClass().getResourceAsStream(fileName);
            if (inputStream == null) {
                throw new ClassNotFoundException(name);
            }
            byte[] b = new byte[inputStream.available()];
            int read = inputStream.read(b);
            return defineClass(name,b,0, b.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name);
        }
    }
}

参考博客:
JVM类加载机制:https://blog.csdn.net/weixin_41812379/article/details/124107027

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

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

相关文章

递归思路讲解

最近刷到了树这一模块的算法题&#xff0c;树相关的算法题几乎都是用递归来实现的&#xff0c;但递归的思路却有点抽象&#xff0c;每次遇到递归&#xff0c;都是通过递归来深度或广度地遍历树&#xff0c;但对于递归遍历树的遍历路线&#xff0c;却有点抽象难懂&#xff0c;不…

基于simulink使用射频模块集天线块对天线阵列的射频系统进行建模

一、前言 本 例 说明 如何 对 包括 天线 阵列 的 MIMO 接收 和 发射 RF 系统 进行 建模。该设计从单个RF链的预算分析开始&#xff0c;然后扩展到多个天线。RF Blockset 天线模块对天线阵列进行全波分析&#xff0c;支持对效应和缺陷进行高保真建模&#xff0c;并结合射频系统的…

2023年的深度学习入门指南(3) - 前端同学如何进行chatgpt开发

2023年的深度学习入门指南(3) - 前端同学如何进行chatgpt开发 在第二篇&#xff0c;我们使用openai的python库封装&#xff0c;搞得它有点像之前学习的PyTorch一样的库。这一节我们专门给它正下名&#xff0c;前端就是字面意义上的前端。 给gpt4写前端 下面我们写一个最土的…

【Web】前端框架对微软老旧浏览器的支持

零、原因 最近要做一个项目&#xff0c;要能在学校机房运行的&#xff0c;也要在手机上运行。电脑和手机&#xff0c;一次性开发&#xff0c;那最好的就是响应式前端框架了。手机和正常的电脑兼容性问题应该都不大&#xff0c;但是学校机房都是Win7的系统&#xff0c;自带的都…

【Linux内核解析-linux-5.14.10-内核源码注释】MM内存管理内核启动初始化源码解析

源码 这是Linux内核中的mm_init函数的代码&#xff0c;其作用是初始化内存管理相关的组件和数据结构。 static: 这是一个函数声明修饰符&#xff0c;表示该函数只在当前文件中可见。 void __init: 这是函数的返回类型和修饰符&#xff0c;表示该函数是内核初始化代码。 page…

SpringCloud详解

SpringCloud是一个基于SpringBoot的分布式系统开发框架&#xff0c;它能够帮助我们快速、稳定地构建分布式系统。本篇博客将对SpringCloud进行详细解析&#xff0c;介绍SpringCloud的主要组件和相关应用场景&#xff0c;同时提供代码示例以帮助读者更好地掌握SpringCloud的实际…

nodejs+vue学生考勤请假管理系统java python php

用户登录模块&#xff1a;用来区分二种用户&#xff0c;学生、管理员。 个人信息管理&#xff1a;用户登录后可以修改用户表中的个人信息。 主页模块&#xff1a;在信息表中读取信息并按照一定模板显示在首页。 信息搜索模块&#xff1a;将信息表中所有信息的标题或内容关键字与…

析构函数/拷贝构造/赋值重载

析构函数&#xff1a; // 析构函数~Stack(){_top 0;_capacity 0;free(_a);_a nullptr;} 1 、2两点与构造函数类似。 3、当我们未显示定义时&#xff0c;编译器会自动生成默认的析构函数。C中&#xff0c;对于内置类型不进行任何处理&#xff0c;对于自定义类型&#xff0…

【SAS应用统计分析】方差分析

声明&#xff1a;本文知识参考内容来自网络&#xff0c;如有侵权请联系删除。 目录 【anova过程】 1.anova过程的语句格式 2.语句说明 【glm过程】 1.glm过程的语句格式 2.语句说明 【实例分析】 【实验步骤】 总结 【anova过程】 SAS系统的START软件提供了anova过程…

TensorRT:自定义插件学习与实践 001

文章简述 本文简单列出了编写Tensorrt插件所需要的关键方法,分为两个部分&#xff0c;一是插件类的具体实现方法&#xff0c;另外是插件工厂的调用方法,插件类最终将编译为.so文件,使用时在c或python中调用,所以插件类的方法调用在其他部分&#xff0c;在本文中难以直观的体现调…

PyQt5

最近在学习pyqt5&#xff0c; 使用pyqt5的时候出现了一些莫名奇妙的问题&#xff0c;解决之后决定把它记录下来&#xff0c;方面pyqt5的初学者使用。 每个问题会按照如下方式进行描述 1、问题描述&#xff1a; 2、解决方法&#xff1a; 问题1&#xff1a; 使用pyinstaller打…

计算机网络笔记:TCP三次握手和四次挥手过程

TCP是面向连接的协议&#xff0c;连接的建立和释放是每一次面向连接的通信中必不可少的过程。TCP连接的管理就是使连接的建立和释放都能正常地进行。 三次握手 TCP连接的建立—三次握手建立TCP连接 ① 若主机A中运行了一个客户进程&#xff0c;当它需要主机B的服务时&#xff0…

迁移学习

迁移学习 什么是迁移学习 迁移学习【斯坦福21秋季&#xff1a;实用机器学习中文版】 迁移学习&#xff08;Transfer Learning&#xff09;是一种机器学习方法&#xff0c;它通过将一个领域中的知识和经验迁移到另一个相关领域中&#xff0c;来加速和改进新领域的学习和解决问…

OS开源项目周报0105

由OpenDigg 出品的iOS开源项目周报第四期来啦。iOS开源周报集合了OpenDigg一周来新收录的优质的iOS开发方面的开源项目&#xff0c;方便iOS开发人员便捷的找到自己需要的项目工具等。 Hero 酷炫的iOS动画引擎 Traits 实时修改原生iOS 应用属性 JSDBanTangHomeDemo 仿半糖首页…

【Git】‘git‘ 不是内部或外部命令,也不是可运行的程序

一、问题 我想利用git clone命令从github上下载项目源代码&#xff0c;发现报错&#xff1a; git 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。我用cmd跑一下git命令&#xff0c;发现报错&#xff1a; 二、问题分析 这个错误提示表明您的系统中没有安装…

Illustrator如何使用基础功能?

文章目录 0.引言1.菜单栏2.工具箱 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对软件界面基本功能进行阐述。    1…

第四章 数据关联分析方法

基本概念和方法 关联规则和算法应用 基本概念和术语 关联规则算法应用&#xff1a; 一个关联规则分析的例子—————超市购物篮分析 不要看 后面数字看不懂 项集&#xff1a;是指项的集合。包含k个项的项集称为k-项集 支持度&#xff1a;若A是一个项集&#xff0c;则A的…

Vue3 +TypeScript 引入 BabylonJs(Vue3实现3D)【一篇文章精通系列】

本文主要介绍如何使用Vue3和TypeScript引入BabylonJs技术实现3D效果。结合实际案例&#xff0c;详细讲解了如何在Vue3项目中引入BabylonJs&#xff0c;并了解其相关知识。通过本文的学习&#xff0c;相信读者可以轻松掌握Vue3实现3D效果以及BabylonJs的相关知识。 Vue3 TypeS…

天梯赛L1-001 ~ 010

&#x1f442; White Lie - Jhameel - 单曲 - 网易云音乐 &#x1f442; 丁丁猫儿 - 施鑫文月 - 单曲 - 网易云音乐 今年蓝桥 / 天梯都陪跑&#xff0c;希望明年&#xff0c;蓝桥杯省一&#xff08;CA组60分&#xff09;&#xff0c;天梯赛国三&#xff08;180分&#xff09;…

详细的实用技巧,让你轻松成为WEB自动化测试大师

目录 一、什么是WEB自动化测试 二、WEB自动化测试工具 三、SeleniumPython环境搭建 1. 安装Python解释器 2. 安装Selenium库 3. 下载浏览器驱动程序 4. 配置环境变量 四、WEB自动化测试实战 1. 编写测试脚本 2. 使用Page Object模式 3. 使用数据驱动测试 五、总结 …