【JVM】Java类加载机制详解

news2025/1/18 9:07:46

【JVM】Java类加载机制详解

文章目录

  • 【JVM】Java类加载机制详解
    • 一:类加载子系统
      • 1:类加载器子系统的作用
      • 2:加载器 ClassLoader 的角色
    • 二:类的加载过程
      • 1:加载阶段
      • 2:验证阶段:确保被加载的类的正确性
      • 3:准备阶段:为类的静态变量分配内存,并将其初始化为默认值
      • 4:解析阶段:把类中的符号引用转换为直接引用
      • 5:初始化阶段
      • 6:总结
    • 三:JVM的类加载器(ClassLoader)分析
      • 1:类加载器分类
      • 2:四种类加载器之间的关系
      • 3:类加载器小结

一:类加载子系统

1:类加载器子系统的作用

  • 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。

  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

  • 加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)

image-20220829192007323

2:加载器 ClassLoader 的角色

image-20220829192338713

  • class file 存在于本地磁盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到 JVM 当中来的,根据这个文件实例化出N个一模一样的实例。

  • class file 加载到JVM中,被称为DNA元数据模板,放在方法区;

  • 在.class文件->JVM->最终成为元数据模板,此过程就要一个运输工具(类装载器 ClassLoader),扮演一个快递员的角色。

二:类的加载过程

当JVM需要用到某个类时,虚拟机会加载它的.class文件,加载了相关的字节码信息后,会为它创建对应的Class对象,而这个过程就被称为类加载。但需额外注意的是:类加载机制只负责class文件的加载,至于是否可以执行,则是由执行引擎决定。接着先看看类加载的过程。如下:

其中类加载的过程包括了加载验证准备解析初始化五个阶段。在这五个阶段中,加载验证准备初始化这四个阶段发生的顺序是确定的,解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

image-20220829192614065

ClassLoader类的作用:

image-20220829192737979

1:加载阶段

  • 通过一个类的全限定获取定义此类的二进制字节流

  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

  • 加载.class文件的方式有:

    • 从本地系统中直接加载;

    • 通过网络获取,典型场景:Web Applet(小程序);

    • 从zip压缩包中读取,比如:jar、war格式的文件

    • 运行时计算生成,使用最多的是:动态代理技术。

2:验证阶段:确保被加载的类的正确性

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:

  • 文件格式验证: 验证字节流是否符合Class文件格式的规范;例如: 是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
  • 元数据验证: 对字节码描述的信息进行语义分析(注意: 对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如: 这个类是否有父类,除了java.lang.Object之外。
  • 字节码验证: 通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证: 确保解析动作能正确执行。

3:准备阶段:为类的静态变量分配内存,并将其初始化为默认值

  • 这时候进行内存分配的仅包括类变量( Class Variables ,即静态变量,被 static 关键字修饰的变量,只与类相关,因此被称为类变量),而不包括实例变量。实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
  • 从概念上讲,类变量所使用的内存都应当在 方法区 中进行分配。不过有一点需要注意的是:JDK 7 之前,HotSpot 使用永久代来实现方法区的时候,实现是完全符合这种逻辑概念的。 而在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。相关阅读:《深入理解Java虚拟机(第3版)》勘误#75open in new window
  • 这里所设置的初始值"通常情况"下是数据类型默认的零值(如 0、0L、null、false 等),比如我们定义了public static int value=111 ,那么 value 变量在准备阶段的初始值就是 0 而不是 111(初始化阶段才会赋值)。特殊情况:比如给 value 变量加上了 final 关键字public static final int value=111 ,那么准备阶段 value 的值就被赋值为 111。

基本数据类型的零值 : (图片来自《深入理解 Java 虚拟机》第 3 版 7.33 )

image-20230129190209842

举例:

变量a在准备阶段会赋初始值,但不是1,而是0,在初始化阶段会被赋值为 1

public class HelloApp {
    private static int a = 1;   //prepare:a = 0 ---> initial : a = 1
    public static void main(String[] args) {
        System.out.println(a);
    }
}

4:解析阶段:把类中的符号引用转换为直接引用

  • 将常量池内的符号引用转换为直接引用的过程

  • 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行

  • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref info等

  • 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄

image-20220829194317101

5:初始化阶段

  • 初始化阶段就是执行类构造器方法()的过程;

  • 此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来;

  • 构造器方法中指令按语句在源文件中出现的顺序执行;

    初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

    • 声明类变量是指定初始值
    • 使用静态代码块为类变量指定初始值

    注意:如果当前类不存在static变量,那么它的字节码文件是不会存在( )

public class ClassInitTest {
    private static int num = 1;

    static {
        num = 3;
        number = 20;
        System.out.println(num); //3
        //System.out.println(number);    //报错:非法的前向引用(可以赋值,但不能调用)
    }

    //linking阶段的prepare环节:number = 0 --> Initialization阶段:20 --> 10
    private static int number = 10;

    public static void main(String[] args) {
        System.out.println(ClassInitTest.num); //3
        System.out.println(ClassInitTest.number); //10
    }
}

image-20220829194904854

注意:若该类具有父类,JVM会保证子类的( )执行前,父类的( )已经执行完毕

public class ClinitTest1 {
    static class Father{
        public static int A = 1;
        static{
            A = 2;
        }
    }

    static class Son extends Father{
        public static int B = A;
    }

    public static void main(String[] args) {
        //加载Father类,其次加载Son类。
        System.out.println(Son.B); //2
    }
}

如上代码,加载流程如下:

  • 首先,执行 main( ) 方法需要加载 ClinitTest1 类
  • 获取 Son.B 静态变量,需要加载 Son 类
  • Son 类的父类是 Father 类,所以需要先执行 Father 类的加载,再执行 Son 类的加载

类初始化时机:

只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

  • 创建类的实例,也就是new的方式
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 使用 java.lang.reflect 包的方法对类进行反射调用时,(如Class.forName(“com.pdai.jvm.Test”)、newInstance())
  • 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化
  • 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
  • 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

6:总结

加载阶段:将.class文件加载到内存中,加载.class文件所需要使用到的类(例如:java/lang/System、java/lang/String等);

链接阶段:为类型变量赋予初始默认值;

初始化阶段:给类型变量赋值(程序员干的事!)(java/lang/System.out: 操作必须要在这一步完成后,才能进行)!

三:JVM的类加载器(ClassLoader)分析

类加载器的任务是,根据一个类的全限定名读取它的二进制字节流数据后,将其加载到内存中并转换为一个与该类对应的Class对象。而虚拟机提供了三种类加载器,同时也可以自己实现,如下:

1:类加载器分类

站在Java开发人员的角度来看,类加载器可以大致划分为以下三类 :

image-20230129192645863

  • 启动类加载器: Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。

    注意:因为JVM是通过全限定名加载类库的,所以,如果你的文件名不被虚拟机识别,就算你把jar包丢入到lib目录下,引导类加载器也并不会加载它。出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类文件。

  • 扩展类加载器: Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

    这个类加载器是由sun公司实现的,位于HotSpot源码目录中的sun.misc.Launcher$ExtClassLoader位置。它主要负责加载<JAVA_HOME>\lib\ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库。它可以直接被开发者使用。

  • 应用程序类加载器: Application ClassLoader,也被称为应用程序类加载器,也是由sun公司实现的,位于HotSpot源码目录中的sun.misc.Launcher$AppClassLoader位置。它负责加载系统类路径java -classpath-D java.class.path指定路径下的类库,也就是经常用到的classpath路径。应用程序类加载器也可以直接被开发者使用。

    一般情况下,该类加载器是程序的默认类加载器,我们可以通过ClassLoader.getSystemClassLoader()方法可以直接获取到它。

  • User 自定义类加载器: Application ClassLoader,也被称为应用程序类加载器,也是由sun公司实现的,位于HotSpot源码目录中的sun.misc.Launcher$AppClassLoader位置。它负责加载系统类路径java -classpath-D java.class.path指定路径下的类库,也就是经常用到的classpath路径。应用程序类加载器也可以直接被开发者使用。

2:四种类加载器之间的关系

如上分析的类加载器关系链如下:

Bootstrap引导类加载器 → Extension拓展类加载器 → Application系统类加载器 → User自定义类加载器

Bootstrap类加载器是在JVM启动时初始化的,它会负责加载ExtClassLoader,并将其父加载器设置为BootstrapClassLoaderBootstrapClassLoader加载完ExtClassLoader后会接着加载AppClassLoader系统类加载器,并将其父加载器设置为ExtClassLoader拓展类加载器。而自己定义的类加载器会由系统类加载器加载,加载完成后,AppClassLoader会成为它们的父加载器。

值得注意的是:类加载器之间并不存在相互继承或包含关系,从上至下仅存在父加载器的层级引用关系。

下面我们通过Java代码来简单剖析一下类加载器之间的关系,案例如下:

// 自定义类加载器
public class ClassLoaderDemo extends ClassLoader {
    public static void main(String[] args){
        ClassLoaderDemo classLoader = new ClassLoaderDemo();

        System.out.println("自定义加载器:" +
                classLoader);
        System.out.println("自定义加载器的父类加载器:" +
                classLoader.getParent());
        System.out.println("Java程序系统默认的加载器:" +
                ClassLoader.getSystemClassLoader());
        System.out.println("系统类加载器的父加载器:" +
                ClassLoader.getSystemClassLoader().getParent());
        System.out.println("拓展类加载器的父加载器:"
                + ClassLoader.getSystemClassLoader().getParent().getParent());
    }
}

输出结果如下:

自定义加载器:com.sixstarServiceOrder.ClassLoaderDemo@6d5380c2
自定义加载器的父类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
Java程序系统默认的加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
系统类加载器的父加载器:sun.misc.Launcher$ExtClassLoader@45ff54e6
拓展类加载器的父加载器:null

因为BootstrapClassLoader是由C++实现的,所以在获取ExtClassLoader的父类加载器时,获取到的结果为null。

3:类加载器小结

JVM的类加载机制是按需加载的模式运行的,也就是代表着:所有类并不会在程序启动时全部加载,而是当需要用到某个类发现它未加载时,才会去触发加载的过程。

Java中的类加载器会被组织成存在父子级关系的层级结构。同时,类加载器之间也存在代理模式,当一个类需要被加载时,首先会依次根据层级结构检查自己父加载器是否对这个类进行了加载,如果父层已经装载了则可以直接使用,反之,如果未被装载则依次从上至下询问,是否在可加载范围,是否允许被当前层级的加载器加载,如果可以则加载。

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

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

相关文章

[leetcode 215] 数组中的第K个最大元素

题目 题目&#xff1a;https://leetcode.cn/problems/kth-largest-element-in-an-array/description/ 解法 这道题目目前快排可以直接过&#xff0c;但是时间复杂度是 O(nlogn)O(nlogn)O(nlogn)。 想要 O(n)O(n)O(n)&#xff0c;这就涉及到408考研知识点了&#x1f602;&…

Java高手速成 | 使用TCP进行手机文件传输

由于TCP是面向流的&#xff0c;这意味着接收端有可能会在一次接收动作中接收两个或者多个数据包&#xff0c;那么当发送方需要把一个大文件分批连续发送时&#xff0c;如何保证接收方能够正确地接收并重修组会成一个完整的文件显得十分重要&#xff0c;本节通过一个端到端的手机…

每天10个前端小知识 【Day 4】

前端面试基础知识题 1. js中如何判断一个值是否是数组类型&#xff1f; instanceof const arr []; arr instanceof Array; // true Array.isArray const arr []; Array.isArray(arr) // true const obj {}; Array.isArray(obj) // false Object.prototype.isPrototype…

拉伯证券|A股延续强势格局 北向资金开年以来净买入超1500亿元

周四&#xff0c;A股商场整体延续强势格局&#xff0c;沪指贴合5日均线震动向上。科技板块仍是干流热点&#xff0c;半导体板块大面积飘红&#xff0c;创新药、CRO等生长赛道均涨势杰出。 到收盘&#xff0c;上证综指报3285.67点&#xff0c;涨0.02%&#xff1b;深证成指报1213…

初识SpringSecurity

初识SpringSecurity spring-security在spring的官网即可找到 spring-security spring-security官方文档的地址 https://docs.spring.io/spring-security/site/docs/ spring-security是Spring系列的关于安全的框架&#xff0c;还有一套安全的框架是Shiro 环境的搭建 项目使…

Android 播放base64音频

需求描述&#xff1a; 做一个Android扫码验证入场的程序&#xff1a; 如果验证通过&#xff0c;则播放一段“验证通过&#xff0c;请放行”的语音&#xff0c; 如果验证不通过&#xff0c;则播放其他的语音提示。 实现步骤&#xff1a; 1.要播放语音提示&#xff0c;先要有…

8个你可能不知道答案的常见JavaScript面试问题

不管你喜不喜欢&#xff0c;棘手的问题仍然会被野外的面试官问到。 原因是&#xff0c;这些问题可以告诉你很多关于你对语言的核心理解&#xff0c;因此你是否适合这份工作。 这些问题中涉及的常见概念包括&#xff1a; Hoisting关闭范围值与引用类型原型继承 今天我们要一石…

AC自动机详解

更好的阅读体验\color{red}{更好的阅读体验}更好的阅读体验 文章目录前置知识字典树 Trie支持操作建字典树实现思想代码实现例题Trie字符串统计最大异或对AC自动机基础概念实现思想代码实现例题搜索关键词单词前置知识 字典树 Trie Trie 是一种能够快速插入和查询字符串的多叉树…

成功解决yum安装的php版本过低的问题

文章目录前言一. 问题复现二. 问题分析三. 问题解决&#xff1a;四. 重要补充1. yum-config-manager介绍2. yum-uitls介绍3. remi资源库总结前言 大家好&#xff0c;我是沐风晓月&#xff0c;日常学习过程经常会遇到一些奇奇怪怪的问题&#xff0c;而解决问题就成了常态&#…

鸿蒙开发学习|HarmonyOS工程介绍

系列文章目录 第一章 HarmonyOS是什么 第二章 基础环境和开发工具 文章目录系列文章目录前言一、HarmonyOS工程介绍二、工程目录结构三、工程目录介绍1.entry2.Ability3.库文件4.资源文件5.配置文件6.pack.info7.HAR总结前言 本文将会给大家梳理 HarmonyOS 源码目录结构&…

关于《利用LexYacc进行词法分析和语法分析并生成语法树》

利用Lex&Yacc进行词法分析和语法分析 写在前面 利用Lex进行词法分析的流程在前面已经讲过&#xff0c;接下来是利用Lex&Yacc进行语法分析&#xff0c;最后可视化生成语法树。具体的操作视频&#xff1a;https://www.bilibili.com/video/BV1wY411q7aH/ 语法分析流程 …

【MySQL】MySQL 8.0 新特性之 - 窗口函数(Window Functions)

窗口函数 - Window Functions1. 定义1.1 窗口函数1.2 语法格式2. 分类2.1 序号函数2.1.1 row_number()2.1.2 rank()2.1.3 dense_rank()2.2.4 示例2.2 分布函数2.2.1 percent_rank()2.2.2 cume_dist()2.3 前后函数2.3.1 lag(expr, n, default)2.3.2 lead(expr, n, default)2.3.3…

致跟我一样苦恼的你们

2023年2月1日&#xff0c;我决定结束实习&#xff0c;回去准备春招和毕设。我把这个决定跟家人和朋友说时&#xff0c;他们似乎是有点不太赞同&#xff0c;他们觉得&#xff1a; “现在工作不好找&#xff0c;你可以先找好下一家公司后再选择离职” “毕设得事情&#xff0c;…

VBA提高篇_17 区域合纵连横,单元格精准定位

文章目录Application.Union方法:Application.Intersect方法:Range.CurrentRegion属性:Range.Resize(3,2)Range.Offset 单元格偏移属性Application.Union方法: 把多个Range联合在一起,作为一个新的Range对象返回 Sub RangeUnionDemo()Dim a&, r1 As Range, r2 As Range, r3 …

魔兽世界服务端AzerothCore+Centos系统+docker编译教程

魔兽世界服务端AzerothCoreCentos系统docker编译教程1.1 准备工作1.1.1 准备1.1.2 安装软件1.1.3 下载源码1.1.4 地图文件1.2 修改配置文件1.2.1 修改环境变量文件1.2.2 修改文件执行权限1.2.3 修改配置文件1.3 编译及启动1.3.1 编译项目1.3.2 启动容器1.3.3 无法启动1. 网络问…

【Java多线程】线程的安全问题

根据上篇文章买票问题举例&#xff0c;还可能出现的问题&#xff1a; 代码如下&#xff1a; class Window1 implements Runnable{private static int ticket 100;Overridepublic void run() {while (true){if (ticket > 0){try {Thread.sleep(100);} catch (InterruptedExc…

网站优化与seo的方法(seo的优化基础)

SEO优化的常规思路&#xff0c;别全以转化为目标 SEO优化作为现在公司推广营销的基础&#xff0c;几乎每个公司都在做这件事。这种优化既可以提升品牌知名度&#xff0c;又能直接给公司带来流量&#xff0c;确实让不少公司感觉很有用。但是在持续的过程中&#xff0c;又会觉得…

ESP32 Arduino 学习篇(五)TFT_eSPI库

前期准备&#xff1a;1.TFT_eSPI库的安装首先在Libraries里面搜索安装TFT_eSPI库到你的工程文件里面。2.TFT_eSPI库的配置文件配置该库有User_Setup.h和 User_Setup_Select.h两个配置文件&#xff0c;支持 ①自定义参数或 ②使用已有配置 驱动TFT屏幕。User_Setup.h — 由自己定…

PCB阻焊层介绍与设计经验总结

&#x1f3e1;《总目录》 目录1&#xff0c;什么是阻焊层2&#xff0c;阻焊层的用途,3&#xff0c;阻焊层的工艺流程4&#xff0c;阻焊设计的注意事项1&#xff0c;什么是阻焊层 阻焊层是顶层或底层布线层表面的顶层保护层&#xff0c;就是PCB表层的绿油层&#xff0c;在阻焊层…

【坤坤讲师--图】Dinic

Dinic是个很神奇的网络流算法。它是一个基于“层次图”的时间效率优先的最大流算法。层次图是什么东西呢?层次,其实就是从源点走到那个点的最短路径长度。于是乎,我们得到一个定理:从源点开始,在层次图中沿着边不管怎么走,经过的路径一定是终点在剩余图中的最短路。(摘自…