JVM高频面试点(一):Java类加载过程

news2025/3/16 23:03:47

1.概述

在 Java 中,类加载过程是指将 Java 类的字节码加载到内存中,并转换为 Java 虚拟机能够识别和执行的数据结构的过程。类加载是 Java 虚拟机执行 Java 程序的必要步骤之一,它负责加载程序中用到的类和接口。下图所示是 ClassLoader 加载一个 .class 文件到 JVM 时需要经过的步骤:

类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期可以简单概括为 7 个阶段::加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中,验证、准备和解析这三个阶段可以统称为连接(Linking)。

其中使用(Using)和卸载(Unloading)不属于类加载过程,这里不需要过多关注,接下来我们就来看看JVM底层是怎么加载class文件的。

2.类加载过程

根据上面的概述可知,JVM加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。过程如下图所示:

2.1 加载(Loading)

加载是“类加载”过程的第一阶段,为了不让大家混淆这两个名字,接下来我都用**装载(Loading)**来叙述类加载过程的第一阶段。所谓装载,简而言之就是将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型即类模板Class对象。

在装载阶段,虚拟机需要完成以下三件事情:

  • 通过一个类的全限定名来获取其定义的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在Java堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口。

装载这一阶段主要是通过类加载器完成的,类加载器是另一个重要的知识点后面我会安排总结。每个 Java 类都有一个引用指向加载它的 ClassLoader。不过数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。这就意味着我们可以自定义一个类加载器来加载类实现隔离可控。

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨qun:Shepherd_126

2.2 链接(Linking)

链接阶段包括三个子阶段:验证、准备和解析。

2.2.1 验证(Verification):

验证阶段主要确保类的字节码符合 Java 虚拟机规范,以及不会危害 Java 虚拟机的安全。主要包括文件格式验证、元数据验证、字节码验证和符号引用验证等。

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

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用 -Xverifynone 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

2.2.2 准备(Preparation):

准备阶段为类的静态变量分配内存,并设置默认初始值。这些变量在编译期间已经确定了初始值,但在此阶段还未赋值静态变量也称为类变量,即被 static 关键字修饰的变量

这里所设置的初始值"通常情况"下是数据类型默认的零值(如 0、0L、null、false 等),而不是被在 Java 代码中被显式地赋予的值。比如我们定义了public static int n=666 ,那么 n 变量在准备阶段的初始值就是 0 而不是 666(初始化阶段才会赋值),因为在这个阶段并不会像初始化阶段中那样会有初始化或者我们自己编写的Java代码被执行。各个数据类型的零值如下所示:

我们前面特别强调是”通常情况“下,这就说明有特殊情况需要注意,比如给 n 变量加上了 final 关键字public static final int n=6那么准备阶段 n 的值就被赋值为 6。还有以下几点需要注意:

  1. 对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。示例如下:

    可以看到局部变量i没有赋值就使用代码爆红编译通不过,而类变量n和全局变量m都没有赋值,但是可以在直接使用,上面代码去掉局部变量i编译通过之后,控制会打印两行1,因为对n,m进行了++操作。

  2. 对于同时被 staticfinal 修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被 final 修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。

    直接编译不通过。

  3. 对于引用数据类型 reference 来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的空值,即 null 。如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的“空”值。

    public class Test {
        private static Integer num;
    
        private int[] arr;
    
        void method() {
            System.out.println(num);
            System.out.println(arr);
        }
    
        public static void main(String[] args) {
            Test test = new Test();
            test.method();
        }
    }
    

    运行代码控制输出为

    null
    null
    
2.2.3 解析(Resolution):

解析阶段将类、接口、字段和方法的符号引用解析为直接引用。符号引用是一组符号来描述所引用的目标,而直接引用是直接指向目标的指针、句柄或偏移量。

2.3 初始化(Initialization):

初始化阶段是类加载过程的最后一个阶段,它负责执行类的静态变量赋值和静态代码块的初始化操作。在此阶段,Java 虚拟机按照程序中定义的顺序执行类的静态变量赋值和静态代码块中的代码。(即:到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。)

初始化阶段的重要工作是执行类的初始化方法: ()方法。该方法仅能由Java编译器生成并由JVM调用,程序开发者无法自定义一个同名的方法,更无法直接在Java程序中调用该方法,虽然该方法也是由字节码指令所组成。它是由类静态成员的赋值语句以及static语句块合并产生的。

() : 只有在给类的中的static的变量显式赋值或在静态代码块中赋值了。才会生成此方法。
() 一定会出现在Class的method表中。

在 Java 中对类变量进行初始值设定有两种方式:

1、声明类变量是指定初始值。

2、使用静态代码块为类变量指定初始值。

public class InitializationTest {
    public static int id = 1;
    public static int number;

    static {
        number = 2;
        System.out.println("father static");
    }
}

<clinit>()该方法仅能由Java编译器生成并由JVM调用,那么我们怎么验证上面案例执行初始化了呢?这时候就需要在IDEA中安装一个插件: jclasslib Bytecode viewer

image-20240314111431693

如果没有外网不能直接在IDEA插件市场下载安装,请自行去网上下载查找安装包本地安装即可。安装好之后,对我们要查看的代码进行编译:

代码编译完之后,按如下点击,如果安装插件没有这个选项,请重启IDEA再看看…

就能看到如下信息了:可以看到有初始化执行的方法:<clinit>

在加载一个类之前,虚拟机总是会试图加载该类的父类,因此父类的总是在子类之前被调用。也就是说,父类的static块优先级高于子类。 接着上面代码,写一个子类继承上面的父类:

public class SubInitialization extends InitializationTest {
    static{
        number = 6;//number属性必须提前已经加载:一定会先加载父类。
        System.out.println("son static{}");
    }

    public static void main(String[] args) {
        System.out.println(number);
    }
}

控制打印如下:

father static
son static{}
6

那是不是所有类编译都会生成<clinit>方法呢?显然不是的。因为初始化这个阶段只是负责执行类的静态变量赋值和静态代码块的初始化操作的。也就是以下情况不会生成此方法:

  • 一个类中并没有声明任何的类变量,也没有静态代码块时
  • 一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时
  • 一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式
public class Test {
    //场景1:对于非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
    public int n = 1;
    //场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法
    public static int n1;
    //场景3:比如对于声明为static final的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
    public static final int n2 = 1;
}

你可以按上面步骤编译之后利用插件看看有没有生成<clinit>进行验证。

接下来谈谈使用static + final修饰的字段的显式赋值的操作,到底是在哪个阶段进行的赋值?

public class Test {
    public static int n = 1;   //在初始化阶段赋值
    public static final int INT_CONSTANT = 10;   //在链接阶段的准备环节赋值

    public static Integer INTEGER_CONSTANT1 = Integer.valueOf(100); //在初始化阶段赋值
    public static final Integer INTEGER_CONSTANT2 = Integer.valueOf(1000); //在初始化阶段赋值

    public static final String s0 = "hello";   //在链接阶段的准备环节赋值
    public static final String s1 = new String("hello"); //在初始化阶段赋值

    public static String s2 = "hello";  //在初始化阶段赋值

    public static final int NUM1 = new Random().nextInt(10);  //在初始化阶段赋值

    static int a = 9;//在初始化阶段赋值
    static final int b = a; //在初始化阶段赋值

}

有两种情况:

1、在链接阶段的准备环节赋值

  • 对于基本数据类型的字段来说,如果使用static final修饰,则显式赋值(直接赋值常量,而非调用方法)通常是在链接阶段的准备环 节进行

  • 对于String来说,如果使用字面量的方式赋值,使用static final修饰的话,则显式赋值通常是在链接阶段的准备环节进行

2、情况2:在初始化阶段()中赋值。排除上述的在准备环节赋值的情况之外的情况。

总结:什么时候在链接阶段的准备环节:给此全局常量附的值是字面量或常量。不涉及到方法或构造器的调用。除此之外,都是在初始化环节赋值的。

什么时候对类进行初始化?

  1. 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
  2. 当调用类的静态方法时,即当使用了字节码invokestatic指令。
  3. 当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者putstatic指令。
  4. 当使用java.lang.reflect包中的方法反射类的方法时。比如:Class.forName(“com.atguigu.java.Test”)
  5. 当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  6. 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化。
  7. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。(涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄对应的类)

下面情况是不会进行类初始化的:

  1. 当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。当通过子类引用父类的静态变量,不会导致子类初始化

  2. 通过数组定义类引用,不会触发此类的初始化

  3. 引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。

  4. 调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

3.总结

需要注意的是,类加载过程是由 Java 虚拟机和类加载器共同完成的。Java 虚拟机提供了规范,定义了类加载过程的各个阶段,而类加载器负责执行具体的加载任务。Java 虚拟机默认提供了多个类加载器,它们按照一定的委托关系组成了类加载器层次结构,每个类加载器负责加载不同位置的类

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

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

相关文章

【滤波专题-第9篇】类EMD分解算法联合小波阈值降噪及MATLAB代码实现(以ICEEMDAN-样本熵-小波阈值降噪方法为例)

今天这篇介绍的算法&#xff0c;由于其高度的灵活性、使用方法的丰富性以及不错的效果&#xff0c;堪称水论文神器。对于需要使用滤波算法的同学们&#xff0c;这篇文章不可错过~ 本篇会提及很多前置概念&#xff0c;比如ICEEMDAN等模态分解算法、熵特征提取、小波阈值滤波等等…

idea项目mapper.xml中的SQL语句黄色下划线去除

问题描述 当我们使用idea开发java项目时&#xff0c;经常会与数据库打交道&#xff0c;一般在使用mybatis的时候需要写一大堆的mapper.xml以及SQL语句&#xff0c;每当写完SQL语句的时候总是有黄色下划线&#xff0c;看着很不舒服。 解决方案&#xff1a; 修改idea的配置 Edi…

SQLiteC/C++接口详细介绍之sqlite3类(八)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;七&#xff09; 下一篇&#xff1a; SQLiteC/C接口详细介绍之sqlite3类&#xff08;八&#xff09;&#xff08;暂未发表&#xff09; 24.sqlite3_cr…

SpringMVC重点记录

目录 1.学习重点2.回顾MVC3.回顾servlet4.初始SpringMVC4.1.为什么要学SpringMVC?4.2.SpringMVC的中重点DispatcherServlet4.3.SpringMVC项目的搭建4.4.MVC框架要做哪些事情?4.5.可能会遇到的问题 5.SpringMVC的执行原理6.使用注解开发SpringMVC7.Controller控制总结8.RestF…

腾讯地图的(地图选点|输入模糊匹配)

1.支持用户输入框输入进行模糊匹配获取详细地址以及经纬度2.支持用户模糊匹配后点击选点获取详细地址以及经纬度 1.支持用户输入框输入进行模糊匹配获取详细地址以及经纬度2.支持用户模糊匹配后点击选点获取详细地址以及经纬度 <template><div class"tencentMap-…

Web 服务器-Tomcat

文章目录 Web服务器一、Tomcat简介二、基本使用三、在IDEA中创建Maven Web项目四、在IDEA中使用Tomcat Web服务器 一、Tomcat简介 二、基本使用 三、在IDEA中创建Maven Web项目 四、在IDEA中使用Tomcat

首个ChatGPT机器人- Figure 01;李开复旗下零一万物推出Yi系列AI大模型API

&#x1f989; AI新闻 &#x1f680; 首个ChatGPT机器人- Figure 01 摘要&#xff1a;Figure 01是一个由初创公司Figure联合OpenAI开发的人形机器人。它展示了与人类和环境互动的能力&#xff0c;可以说话、看东西&#xff0c;并且可以执行各种任务&#xff0c;如递食物、捡垃…

SIP调试之SIPP测试工具

SIPP是针对SIP协议的一个性能测试的命令行工具&#xff0c;可以动态显示测试的统计信息&#xff08;如呼叫速率、延时、消息统计等&#xff09;。用户可以通过XML场景配置文件&#xff0c;自定义模拟各种UAC/UAS测试场景的信令交互流程&#xff0c;可以被用来测试IP话机、SIP代…

初识微信小程序之swiper和swiper-item的基本使用

在我还没接触到微信小程序之前&#xff0c;通常使用轮播要么手写或使用swiper插件去实现&#xff0c;当我接触到微信小程序之后&#xff0c;我看到了微信小程序的强大之处&#xff0c;让我为大家介绍一下吧&#xff01; swiper与swiper-item一起使用可以做轮播图 基本使用&…

软件无线电系列——带通信号采样定理

本节目录 一、带通信号采样定理 1、带通信号采样定理的定义 2、带通信号采样定理的证明本节内容 一、带通信号采样定理 1、带通信号采样定理的定义 Nyquist采样定理是对频谱分布在(0,fH)上的基带信号的采样分析的&#xff0c;如果信号的频谱分布在某一限定的频带(fL,fH)上&…

【unity与android的交互(一)】安卓打包相关的常见参数详解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

hadoop单机ssh免密登录

1. 在hadoop目录下生成密钥对 [rootmaster centos]# cd /usr/apps/hadoop-2.7.1/ [rootmaster hadoop-2.7.1]# ssh-keygen -t rsa //在hadoop目录下生成密钥对 2.找到密钥对的位置 [rootmaster hadoop-2.7.1]# find / -name .ssh //找到密钥对的位置 cd [rootmaster hadoo…

vscode 运行 java 项目之解决“Build failed, do you want to continue”的问题

Visual Studio Code运行 java 起来似乎比 IDEA 更轻量、比 eclipse 更友好&#xff0c;是不可多得的现代编译法宝。 安装好官方推荐的 java 扩展包后&#xff0c;就可以运行 java 代码了。功能 比 code runner 强&#xff0c;支持 gradle、maven、普通java项目&#xff0c;运行…

深度学习 Day27——J7对于ResNeXt-50算法的思考

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff1a;K同学的学习圈子 文章目录 前言问题分析 前言 关键问题&#xff1a;ResNeXt-50中conv_shortcutFalse时…

Windows蓝牙驱动开发之模拟HID设备(一)(把Windows电脑模拟成蓝牙鼠标和蓝牙键盘等设备)

by fanxiushu 2024-03-14 转载或引用请注明原作者 把Windows电脑模拟成蓝牙鼠标和蓝牙键盘&#xff0c;简单的说&#xff0c;就是把笨重的PC电脑当成鼠标键盘来使用。 这应该是一个挺小众的应用&#xff0c;但有时感觉也应该算比较好玩吧&#xff0c; 毕竟实现一种一般人都感觉…

中值定理j

f ( n ) ( ξ ) 0 f^{(n)}(\xi)0 f(n)(ξ)0

MATLAB编译器配置:MinGW

使用 MATLAB 2022b版本&#xff0c;查询编译器时如上&#xff0c;想安装个MinGW编译器&#xff0c;自带的附加资源管理不好使&#xff0c;只能换个别的法子&#xff0c;经过一些参考&#xff0c;总结如下。 步骤1.在这里下载一个MinGW.最新版本是10.3.0.然后默认安装&#xff…

获取源数据推送到Kafka

打开BigData-KongGuan项目 打开BigData-KongGuan项目&#xff0c;在上一个任务&#xff08;“用户登录”&#xff09;的基础上继续完成本阶段任务。初始化加载SpringBoot项目的代码所在位置src/main/java/com/qrsoft/BigDataKongGuanApplication.java &#xff0c;代码如下&am…

Flutter:构建美观应用的跨平台方案

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Redis:ClassCastException【bug】

Redis&#xff1a;ClassCastException【bug】 前言版权Redis&#xff1a;ClassCastException【bug】错误产生相关资源控制器&#xff1a;UserController("/user")配置&#xff1a;RedisConfiguration实体类&#xff1a;User数据表&#xff1a;User 解决 最后 前言 2…