JVM类加载机制—JVM类加载过程

news2024/12/29 9:01:37

一、概述

代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class)。而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验、转换解析、初始化,使这些数据最终成为可以被JVM直接使用的Java类型,这个说来简单但实际复杂的过程叫做JVM的类加载机制。

二、类加载过程

以自定义的Math类为例,分析类加载全过程。实例代码如下:

public class Math {
	public static final int initData = 666;
	public static User user = new User();

	public int compute() { //一个方法对应一块栈帧内存区域
		int a = 1;
		int b = 2;
 		int c = (a + b) * 10;
 		return c;
 	}

 	public static void main(String[] args) {
 		Math math = new Math();
 		math.compute();
 	}

 }

以上面Math类为例,类加载全过程流程如下

1、JVM加载过程分析

其中在JVM中通过类加载器ClassLoader的loadClass方法对类进行装载。loadClass方法加载过程有以下几步:加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载。

1.1 加载

所谓加载不是指的类加载机制,只是类加载机制中的第一步加载。这个阶段就是将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型——类模板对象

类模板对象,其实就是Java类在JVM内存中的一个快照,JVM将从字节码文件中解析出的常量池、类字段、类方法等信息存储到类模板中,这样JVM在运行期便能通过类模板调用Java类中的任意信息。创建的类模板对象的结构存储在方法区中。

一般来说加载分为以下几步:
a. 通过一个类的全限定名(包名与类名)获取此类的二进制字节流(Class文件)。
b.  将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。这里只是转化了数据结构,并未合并数据。(方法区就是用来存放已被加载的类信息,常量,静态变量,编译后的代码的运行时内存区域)
c.  在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。这个Class对象并没有规定是在Java堆内存中,它比较特殊,虽为对象,但存放在方法区中

1.2 验证

验证被加载后的类是否有正确的结构,类数据是否会符合虚拟机的要求,确保不会危害虚拟机安全。

验证作为链接的第一步,用于确保类或接口的二进制表示结构上是正确的,从而确保字节流包含的信息对虚拟机来说是安全的。

Java虚拟机规范中关于验证阶段的规则也是在不断增加的,但大体上会完成下面4个验证动作。

a. 文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前版本的虚拟机处理。
b.  元数据验证:主要对字节码描述的信息进行语义分析,以保证其提供的信息符合Java语言规范的要求。
c. 字节码验证:主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型做完校验后,字节码验证将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
d. 符号引用验证:最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段解析阶段发生。符号引用是对类自身以外(常量池中的各种符号引用)的信息进行匹配校验。

1.3 准备

给类或接口的静态变量分配内存空间,并且默认初始化这些字段。  并赋予默认值 如 int类型为0,String类型为null, boolean 类型为false等

1.4 解析

将常量池中的符号引用替换为直接引用过程。该阶段会把一些静态方法(符号引用 如main()方法)替换为执行数据所存内存的指针或句柄(直接引用),这就是所谓的静态链接过程(类加载期间完成)。动态链接实在程序运行期间完成的将符号引用替换为直接引用(如 实际使用的方法 math.compute())。

符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要可以唯一定位到目标即可。符号引用于内存布局无关,所以所引用的对象不一定需要已经加载到内存中。各种虚拟机实现的内存布局可以不同,但是接受的符号引用必须是一致的,因为符号引用的字面量形式已经明确定义在Class文件格式中。

直接引用(Direct References):直接引用时直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关,同一个符号引用在不同虚拟机上翻译出来的直接引用一般不会相同。如果有了直接引用,那么它一定已经存在于内存中了。

1.5 初始化

初始化是类加载的最后一步。除了加载阶段,用户可以通过自定义的类加载器参与,其他阶段都完全由虚拟机主导和控制。到了初始化阶段才开始真正执行Java代码。

初始化主要工作就是对类的静态变量初始化为指定的值(如initData = 666)和执行静态代码块。

注意:主类在运行过程中如果使用到其它类,会逐步加载这些类。jar包或war包里的类不是一次性全部加载的,是使用到时才加载。

三、类加载时机

Java虚拟机规范中严格规定了有且只有五种情况必须对类进行初始化

a. 使用new创建类的实例或使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),以及使用invokestatic调用一个静态方法的时候,对应类必须进行过初始化。

b. 通过java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则要首先进行初始化。

c. 当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化。

d. 当虚拟机启动时,用户需要指定一个主类(包含main()方法的类),虚拟机会首先初始化这个类。

e. 使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。

注意:虚拟机规范使用了“有且只有”这个词描述,这五种情况被称为“主动引用”,除了这五种情况,所有其他的类引用方式都不会触发类初始化,被称为“被动引用”。

1、主动引用情况的示例

new 对象触发类加载

/**
 * 分析JVM内存模型运行
 */
public class Math {
    private final static int initData = 666;
    private static User user = new User();


    public int compute(){       //一个方法对应一个块栈帧内存区域
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }

    public static void main(String[] args) {
        Math m = new Math();
        int result = m.compute();
        System.out.println(result);
    }

    static {
        System.out.println(initData);
    }
}

运行结果

反射触发类加载

示例代码如下

public class ReflectTest {
    static {
        System.out.println("ReflectTest static block");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("com.test.jvm.classloader.ReflectTest");
    }
}

代码运行结果

加载子类会先加载父类

实例代码如下

public class ExtendTest {

    public static void main(String[] args) {
        System.out.println(new B().str);

    }
    //父类
    static class A {

        static {
            System.out.println("A static block");
        }

    }
    //子类
    static class B extends A {

        public String str = "B str";
        static {
            System.out.println("B static block");
        }
    }
}

示例运行结果

主类触发类加载

示例代码如下

public class HeapTest {

    private final String str = "0987654321";
    private final int data = 123456789;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("验证堆内存溢出情况");
        List<HeapTest> heapTestList = new ArrayList<HeapTest>();
        while (true){
            HeapTest heapTest = new HeapTest();
            heapTestList.add(heapTest);
            //Thread.sleep(10);
        }

    }
}

示例运行结果

2、被动引用情况的示例

子类引用父类静态字段

通过子类引用父类的静态字段,对于父类属于“主动引用”的第一种情况,对于子类,没有符合“主动引用”的情况,故子类不会进行初始化。代码如下

public class ExtendTest {

    public static void main(String[] args) {
        //System.out.println(new B().str);
        System.out.println(B.value);

    }
    //父类
    static class A {
        //静态变量
        public static int value = 666;

        static {
            System.out.println("A static block");
        }

    }
    //子类
    static class B extends A {

        public String str = "B str";
        static {
            System.out.println("B static block");
        }
    }
}

示例运行结果

数组引用类

通过数组来引用类,不会触发类的初始化,因为是数组new,而类没有被new,所以没有触发任何“主动引用”条款,属于“被动引用”。代码如下:

//数组测试类
public class ArrayTest {
    //静态变量value
    public static int value = 666;
    //静态块,父类初始化时会调用
    static{
        System.out.println("父类初始化!");
    }
}

//主测试类
public class Test {
    public static void main(String[] args) {
        ArrayTest[] tests = new ArrayTest[10];
    }
}

运行结果

引用静态变量

静态常量在编译阶段就会被存入调用类的常量池中,不会引用到定义常量的类,这是一个特例,需要特别记忆,不会触发类的初始化!

//静态变量测试类
public class ConstClass {
    static{
        System.out.println("常量类初始化!");
    }

    public static final String HELLOWORLD = "hello world!";
}

//主测试类
public class Test {
    public static void main(String[] args) {
        //ArrayTest[] tests = new ArrayTest[10];
        System.out.println(ConstClass.HELLOWORLD);
    }
}

运行结果

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

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

相关文章

数据结构--图(笔记)

文章目录 1. 概念2. 分类无向图有向图循环图连通图 3. 应用4. 操作(CRUD)5. 图常见的数据结构邻接表邻接矩阵关联矩阵关联矩阵与邻接矩阵 6. 内容出处 1. 概念 ① 图&#xff1a;在计算机科学中&#xff0c;图&#xff08;英语&#xff1a;graph&#xff09;是一种抽象数据类型…

36. 有效的数独【 力扣(LeetCode) 】

一、题目描述 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图…

2-72 基于matlab的平稳小波变换进行多聚焦图像融合

基于matlab的平稳小波变换进行多聚焦图像融合&#xff0c;获得一副清晰的图像&#xff0c;带有一副示例图像&#xff0c;实验效果好。SWT级平稳小波变换&#xff0c;是一种多尺度、多方向、时频局部的图像稀疏表示方法&#xff0c;广泛运行图像处理领域&#xff0c;具有平移不变…

msxml*.dll 错误 ‘80072f7d‘ 安全频道支持出错 解决方案

诡异的 msxml6.dll错误 80072f7d安全频道支持出错&#xff0c;用 SSLTools.exe 修复的方法无效&#xff01;&#xff01;&#xff01; ’--------------------------------------------------------------- 有如下简要 ASP 代码&#xff0c;用于获取网页链接返回内容&#xf…

《图解设计模式》笔记(四)分开考虑

九、Bridge模式&#xff1a;将类的功能层次结构与实现层次结构分离 类的两个层次结构和作用 类的功能层次结构&#xff1a;希望增加新功能时 父类有基本功能&#xff0c;在子类中增加新功能 Something父类 …├─SomethingGood子类 想要再增加新功能 Something父类 …├─So…

计算机的错误计算(六十九)

摘要 计算机的错误计算&#xff08;六十三&#xff09;与&#xff08;六十八&#xff09;分别探讨了大数与 附近数 的余切函数值的错误计算。本节讨论第三种类型数值&#xff1a; 附近数 的余切函数的计算精度问题。 例1. 已知 计算 不妨先用 Python的 torch库计算&…

RocketMQKafka重试队列

为实现服务间的解耦和部分逻辑的异步处理&#xff0c;我们的系统采纳了消息驱动的方法。通过消息队列的使用&#xff0c;各个服务能够基于事件进行通信&#xff0c;从而降低了直接的依赖关系&#xff0c;优化了系统的响应性能和可靠性。 为什么需要考虑消费重试&#xff1f; …

人格凭证(PHC):一种鉴别AI防伪保护隐私的真实身份验证技术

人格凭证&#xff08;PHC&#xff09;&#xff1a;一种鉴别AI防伪保护隐私的真实身份验证技术 引言 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;网络空间中的身份验证问题日益凸显。AI不仅能模仿人类行为&#xff0c;还能创建虚假账户、发布误导性信息…

秒懂Linux之缓冲区

目录 一.何为缓冲区 二. 缓冲区在哪 三. 模拟编码 一.何为缓冲区 缓冲区说白了就是一块内存区域&#xff0c;目的是为了提高使用者的效率以及减少C语言接口的使用频率~ 下面我们用一则小故事来类比出缓冲区的功能~ 张三为了给朋友李四庆祝生日快乐准备了份生日礼物~张三难道…

开源原型设计工具Penpot

Penpot是一个现代化、开源的协同设计平台&#xff0c;专为跨职能团队打造&#xff0c;提供了强大的在线设计和原型制作功能。 以下是对Penpot的详细介绍&#xff1a; 一、平台特点 开源与免费&#xff1a;Penpot是一个完全免费且开放源代码的项目&#xff0c;允许社区贡献和定…

Redis补充

Redis事务 Redis事务的概念 Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令&#xff0c;一个事务中所有命令都会被序列化。在事务执行过程&#xff0c;会按照顺序串行化执行队列中的命令&#xff0c;其他客户端提交的命令请求不会插入到事务执行命令序列中。 …

JAVA多线程等待唤醒机制

为什么要处理线程间通信&#xff1a; 当我们需要多个线程来共同完成一件任务&#xff0c;并且我们希望他们有规律的执行&#xff0c;那么多线程之间需要一些通信机制&#xff0c;可以协调它们的工作&#xff0c;以此实现多线程共同操作一份数据。 比如&#xff1a;线程A用来生…

Java | Leetcode Java题解之第357题统计各位数字都不同的数字个数

题目&#xff1a; 题解&#xff1a; class Solution {public int countNumbersWithUniqueDigits(int n) {if (n 0) {return 1;}if (n 1) {return 10;}int res 10, cur 9;for (int i 0; i < n - 1; i) {cur * 9 - i;res cur;}return res;} }

4-1-5 步进电机原理2(电机专项教程)

4-1-5 步进电机原理2&#xff08;电机专项教程&#xff09; 4-1-5 步进电机原理2永磁式步进电机反应式步进电机混合式步进电机混合式步进电机基本原理 4-1-5 步进电机原理2 新的步进电机分类 永磁式步进电机 目前学习的转子都是永磁铁 反应式步进电机 软磁材料易受到周围磁场…

阿里云魏子珺:阿里云Elasticsearch AI 搜索实践

作者&#xff1a;阿里云魏子珺 【AI搜索 TechDay】是 Elastic 和阿里云联合主办的 AI 技术 Meetup 系列&#xff0c;聚焦企业级 AI 搜索应用和开发者动手实践&#xff0c;旨在帮助开发者在大模型浪潮下升级 AI 搜索&#xff0c;助力业务增长。 阿里云 Elasticsearch 的 AI 搜索…

Nginx笔记(高级)

扩容 通过扩容提升整体吞吐量 单机垂直扩容&#xff1a;硬件资源增加 云服务资源增加 整机&#xff1a;IBM、浪潮、DELL、HP等CPU/主板&#xff1a;更新到主流网卡&#xff1a;10G/40G网卡磁盘&#xff1a;SAS(SCSI) HDD&#xff08;机械&#xff09;、HHD&#xff08;混合&…

OpenCV几何图像变换(5)旋转和缩放计算函数getRotationMatrix2D()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算二维旋转的仿射矩阵。 该函数计算以下矩阵&#xff1a; [ α β ( 1 − α ) ⋅ center.x − β ⋅ center.y − β α β ⋅ center.x ( …

Linux 中断处理与内核线程化——以触摸屏中断为例

文章目录 1 什么是中断&#xff1f;2 传统的中断处理模型3 内核线程与用户进程4 中断线程化的理念5 devm_request_threaded_irq 与 request_irq 的比较6 触摸屏驱动中的中断线程化参考链接封面 本文探讨了 Linux 中断处理的传统模型与中断线程化的理念&#xff0c;以及在触摸屏…

【Python】计算直角三角形的 ∠MBC

有一个直角三角形 ABC&#xff0c;其中角 B 是直角&#xff08;90&#xff09;。点 M 是斜边 AC 的中点。我们需要根据边 AB 和 BC 的长度来计算角 ∠MBC。 在直角三角形中&#xff0c;如果一个角是直角&#xff0c;那么另外两个角的和是90。由于 M 是斜边的中点&#xff0c;根…

turtle画图知识

Turtle库是Python编程语言中的一个库&#xff0c;用于创建各种类型的图形&#xff0c;包括简单圆形、线条、路径和图片。它支持多种图形类型&#xff0c;并且可以绘制出各种复杂的形状。 以下是一些基本的使用方法&#xff1a; 1. 创建一个新的Turtle对象&#xff1a; pytho…