JVM—虚拟机类加载时机与过程

news2024/9/22 3:42:51

参考资料:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明

1. 类加载的时机

一个类型从被加载到虚拟机内存开始,到卸载出内存为止,它的生命周期会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析统称为链接。

除了初始化外,其他阶段的顺序是按部就班的开始,注意是开始而不是执行,因为这些阶段都是混合交叉的进行。

关于什么阶段加载、JVM规范没有强制约束,而是交给虚拟机自由把握,但是JVM规范严格规定了六种情况必须立即对类进行初始化(加载、验证、准备、解析自然在此之前)

  1. 遇到new、static、putstatic、invokestatic这四条字节码指令时。

  2. 反射调用时

  3. 初始化类时,其父类还未初始化,则需要先触发父类的初始化。

  4. JVM启动时用户指定执行的主类(包含main方法的类)

  5. 接口中定义JDK8加入的默认方法(被Default方法修饰的接口方法),这个接口类必须在实现类之前初始化。

  6. JDK7之后的动态语言支持.......

被动引用的例子如下:

1、通过子类引用父类的静态属性,子类不会触发初始化,只会触发父类的初始化。

public class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}

public class SubClass extends SuperClass{
    static {
        System.out.println("SubClass init!");
    }
}

public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
    }
}
// 输出结果:
SuperClass init!
123

2、用数组定义引用类不会触发初始化

class NotInitialization {
    public static void main(String[] args) {
        SuperClass[] classes = new SuperClass[10];
    }
}
//该代码没有任何输出

3、常量在编译阶段进入调用类的常量池,本质上没有直接引用到定义常量的类,因此不会触发定义常量的初始化。

class ConstClass {
    static {
        System.out.println("ConstClass init!");
    }

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

class NotInitialization {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLOWORLD); //hello world
    }
}

2. 类加载过程

2.1 加载

加载阶段是整个类加载过程中的一个阶段需要注意。

在加载阶段JVM需要完成以下三件事:

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

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

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

对于数组类不是通过类加载器创建的,而是由JVM在内存中动态构建的。但是数组类最终还是由类加载器完成加载。

2.2 验证

为了确保Class文件的字节流中包含的信息符合JVM的规范约束要求,确保不会危害虚拟机。

验证阶段会完成以下四个检验动作。

2.2.1 文件格式规范

验证字节流是否符合Class文件格式规范,例如:魔点、主次版本号、常量池等等,参考类文件结构

只有通过验证字节流才会进入JVM内存的方法区读取,所以后面三个验证阶段都是基于方法区内存结构进行的

2.2.2 元数据验证

对字节码描述的信息进行语义分析校验,这个阶段验证点如下:

  1. 验证是否具有父类

  2. 是否继承不允许继承的类(final修饰的类)

  3. 如果这个类不是抽象类,是否实现了父类/接口中的所有方法

  4. 类的字段、方法是否与父类矛盾

2.2.3 字节码验证

这是验证过程中最复杂的,主要目的是通过数据流分析和控制流分析,确定程序语义合法性

在第二阶段对元数据信息中数据类型校验完毕后对类的方法体(Class文件中的Code属性)进行校验分析。例如:

保证方法体中的类型转化总是有效的,例如把子类对象赋值给父类数据类,但不能把父类赋值给子类,或者毫不相干的数据类型。

2.2.4 符号引用验证

最后一个阶段的校验行为发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段—解析过程中发生。

  • 符号引用验证就是验证该类是否缺少或者被禁止访问它依赖的某些外部类,方法,字段等资源。

符号引用验证需要检验以下内容:

  1. 通过字符串描述的全限定名是否可以找到对应的类

  2. 指定类是否存在符合方法的字段描述符

  3. 符号引用中类、字段、方法的可访问性,是否可被当前类访问。

2.3 准备

准备阶段是正式为类中定义的类变量(static变量)分配内存并设置初始值的阶段,这些在JDK7及之前放在了方法区中,JDK8及之后放在了Java堆中。

  • 需要注意的是这里分配的内存仅仅是给类变量分配内存,而实例变量在运行时随着对象实例化才会分配内存。

  • 这里所说的初始状是0,例如public static int i = 123,在准备阶段完成后初始值是0而不是123。

  • 如果类字段的字段属性表中存在ConstantValue的属性,那么就会被初始化为指定的值,例如:public static final int i = 123

2.4 解析

解析阶段是将常量池内的符号引用替换为直接引用的过程,解析过程的符号引用和直接引用关联如下:

  • 符号引用:符号引用是以一组符号来描述引用的目标,可以是任何形式的字面量。和JVM内存布局无关。

  • 直接引用:直接引用是可以指向目标的指针,相对偏移量或者一个句柄。和JVM内存布局有关,同一个符号在不同虚拟机翻译的直接引用一般不同。

2.4.1 类或接口的解析

假设当前为D类,如果把一个未解析过的符号引用N解析为一个类或者接口C的直接引用,那么JVM需要完成以下3个步骤:

  1. 如果C不是一个数组类型,虚拟机会将代表N的全限定名传递给D的类加载器,去加载这个类。在加载过程中可能触发其他类的加载,一旦出现问题那么解析失败。

  2. 如果C是一个数组类型,并且元素类型为对象。那么就会按照第一点的方式去加载这个类。

  3. 前两步没有异常。那么C一个在JVM成为了一个有效的类/接口,但在解析完成前还需进行符号引用验证。

2.4.2 字段解析

在解析一个字段之前,首先对字段表内的符号引用解析,也就是字段所属的类或者接口的符号引用。接下来按照如下步骤对C后续的字段搜索:

  1. 如果C本身包含简单名称和字段描述符都与目标匹配的字段,那么直接返回这个字段的直接引用。

  2. 否则、如果C中实现接口,那么会按照继承关系从下往上一个一个搜索,如果接口出现了相匹配的字段,那么返回这个直接引用。

  3. 否则、如果C不是Object的话,也会按照继承关系向上搜索,直到出现匹配的字段。

  4. 否则、查找失败抛出NoSuchAccessError

2.4.3 方法解析

第一个步骤和字段解析一样,解析方法所属的类/接口的引用。解析成功依然用C表示这个类。

2.4.4 接口方法解析

和上面一样。

2.5 初始化

初始化是类加载过程中最后一个动作,在初始化中JVM才将开始执行类中编写的java代码逻辑,将控制权交给应用程序。

在准备阶段时,变量已经赋值过一次系统要求的初始零值,而在初始化阶段就复制为编码制定的值。

1、非法向前引用—Illegal forward reference

static {
    i = 0;
    System.out.println(i);
}

static int  i = 1;

2、<clinit>方法的执行顺序

<clinit>方法与实例构造器<init>方法不同,它不需要显示的调用父类的构造器。

JVM会保证父类的<clinit>方法一定先于子类执行,也就是父类静态代码块执行先于子类,在下面这个例子中也就是说父类静态代码块优于子类的赋值操作。

public class Main {
    public static void main(String[] args) {
        System.out.println(Sub.B); // 2
    }
}

class Parent{
    public static int A = 1;
    static {
        A = 2;
    }
}

class Sub extends Parent{
    public static int B = A;
}

3、字段解析

JVM必须保证一个类的<clinit>方法在多线程下被正确的加锁同步,如果多个线程同时初始化一个类,那么只有一个线程才去执行这个类的<clinit>方法,其他线程都被阻塞。

  • 所以一个类的<clinit>方法中有很多耗时操作就会导致多个线程阻塞。

public class DeadLoopClass {
    static {
        if (true) {
            System.out.println(Thread.currentThread() + "init DeadLoopClass");
            while (true);
        }
    }

    public static void main(String[] args) {
        Runnable script = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread() + " start");
                new DeadLoopClass();
                System.out.println(Thread.currentThread() + " run over");
            }
        };

        new Thread(script).start();
        new Thread(script).start();

    }
}

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

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

相关文章

netapp内网穿透

1. 注册netapp账号 NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 2. 购买隧道&#xff0c;要求不高的话可以使用这个免费的 3.设置隧道 主要设置你想通过公网访问你的本地端口号 4.点击我的隧道&#xff0c;注意这里的authtoken&#xff0c;后面会用到 5.本地下载netap…

[E二叉树] lc572. 另一棵树的子树(dfs+前中序判断+树哈希+树上KMP+好题)

文章目录 1. 题目来源2. 题目解析 1. 题目来源 链接&#xff1a;572. 另一棵树的子树 2. 题目解析 看到这个题目就感觉不简单&#xff0c;因为写了写 dfs 版本的&#xff0c;发现好像不太会… 还是简单粗暴一点&#xff0c;直接搞一个 前序中序&#xff0c;进行判断即可。我…

应急响应-Web3

打开虚拟机之后&#xff0c;运行解题系统&#xff1a; 共有三个问题&#xff01; 攻击者的两个IP地址 首先我们看到机器的桌面上还是存在phpstudy&#xff0c;那就还是先去看看是不是从web层面进行的攻击&#xff0c;上传webshell从而getshell。 利用D盾尝试对phpstudy目录进…

Python | Leetcode Python题解之第319题灯泡开关

题目&#xff1a; 题解&#xff1a; class Solution:def bulbSwitch(self, n: int) -> int:return int(sqrt(n 0.5))

redis面试(四)持久化

什么是持久化&#xff1f; 由于redis是基于内存操作的轻量型数据库&#xff0c;所以如果发生宕机重启这种事情&#xff0c;存储的数据就会直接丢失&#xff0c;如果在里面存储了没有备份的数据&#xff0c;那么确实会对我们的业务造成一定影响。 所以我们要通过持久化的手段&a…

Java中interrupted()与isInterrupted()的区别

Java中interrupted&#xff08;&#xff09;与isInterrupted&#xff08;&#xff09;的区别 1、interrupted()方法1.1 示例 2、isInterrupted() 方法2.1 示例 3、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java多线程编程中&a…

手持式气象站:科技赋能精准气象观测

在自然界与人类社会的交织中&#xff0c;气象条件始终扮演着至关重要的角色。无论是农业生产、城市建设&#xff0c;还是日常生活、户外活动&#xff0c;都离不开对天气变化的准确预测和及时响应。随着科技的飞速发展&#xff0c;气象观测设备也迎来了变化&#xff0c;其中&…

什么是人工智能 (AI)

1955年9月&#xff0c;达特茅斯学院&#xff08;Dartmouth College&#xff09;年轻的数学助理教授约翰麦卡锡&#xff08;John McCarthy&#xff09;大胆提出&#xff0c;“原则上&#xff0c;学习的各个方面或智力的任何其他特征都可以被精确地描述&#xff0c;以至于可以制造…

使用Python3脚本检查节假日并通过企业微信发送每日信息

文章目录 简介环境配置企业微信机器人创建群聊设置机器人信息 脚本详解导入必要的库获取节假日信息判断是否为工作日或节假日获取天气预报获取每日一句发送消息到微信主函数 加入定时任务总结完整代码 简介 在日常工作和生活中&#xff0c;自动化任务可以帮助我们节省大量时间…

吃惊!这个Windows双系统方法逆天了|UEFI篇

前言 最近小白在折腾别的系统教程&#xff0c;偶然间发现居然有一个很nice的Windows双系统教程。于是于是&#xff0c;果断尝试了一下&#xff0c;发现真的很可行&#xff01; 这个双系统的办法并不需要使用到WinPE系统&#xff0c;因此并不需要使用到U盘&#xff0c;只需要在…

科普文:微服务之SpringBoot性能优化器动态线程池【Dynamic-Tp】特性和源码解读

一、简述 gitee地址&#xff1a;https://gitee.com/yanhom/dynamic-tp github地址&#xff1a;https://github.com/lyh200/dynamic-tp dynamic-tp是一个轻量级的动态线程池插件&#xff0c;它是一个基于配置中心的动态线程池&#xff0c;线程池的参数可以通过配置中心配置进…

数的三次方根

题目 给定一个浮点数 n&#xff0c;求它的三次方根。 输入格式 共一行&#xff0c;包含一个浮点数 n。 输出格式 共一行&#xff0c;包含一个浮点数&#xff0c;表示问题的解。 注意&#xff0c;结果保留 6 位小数。 数据范围 输入样例&#xff1a; 1000.00 输出样例&a…

征服数据结构中的时间和空间复杂度

目录 时间复杂度推导大O方法求解时间复杂度的方法普通顺序结构单循环双循环递归Master定理&#xff08;主定理&#xff09;递归树方法 空间复杂度 一个算法的好坏根据什么来判断呢&#xff1f;有两种一种是时间效率&#xff0c;一种是空间效率。时间效率也可称为时间复杂度&…

内网穿透--LCX+portmap转发实验

实验背景 通过公司带有防火墙功能的路由器接入互联网&#xff0c;然后由于私网IP的缘故&#xff0c;公网 无法直接访问内部web服务器主机&#xff0c;通过内网其它主机做代理&#xff0c;穿透访问内网web 服务器主机 实验设备 1. 路由器、交换机各一台 2. 外网 kali 一台&…

网络层和数据链路层的理解

文章目录 网络层IP协议网段划分IP地址数量问题NAT技术DNSICMP协议 数据链路层以太网MTU的影响ARP协议 网络层 作用&#xff1a; 在网络环境中确定消息传输的路径。 主要协议&#xff1a; IP协议。 IP协议 IP协议的基本概念&#xff1a;凡是入网的机器都会有一个IP地址&#…

手机上音乐如何转换成MP3格式?分享5款音频格式转换APP

手机上音乐如何转换成MP3格式&#xff1f;相信很多外出办公或者不经常使用电脑的工作人士&#xff0c;学生党&#xff0c;媒体从业者都有这样的疑惑和需求。不同设备和应用可能支持不同的音频格式&#xff0c;导致某些情况下需要将音乐文件转换为MP3格式以确保兼容性。下面&…

24暑假算法刷题 | Day27 | 贪心算法 I | LeetCode 455. 分发饼干,376. 摆动序列,53. 最大子数组和

目录 455. 分发饼干题目描述题解 376. 摆动序列题目描述题解 53. 最大子数组和题目描述题解 455. 分发饼干 点此跳转题目链接 题目描述 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#x…

【Mind+】掌控板入门教程03 节日的祝福

在节日的时候&#xff0c;我们通常会送朋友或者家人一张贺卡表达美好的祝福。随着科技的发展&#xff0c;我们已经可以通过手机聊天工具发送一封电子贺卡。电子贺卡相当于把祝福做成了一个小动画&#xff0c;它环保方便&#xff0c;生动有趣。今天就让我们用掌控板来制作一份电…

Java | Leetcode Java题解之第318题最大单词长度乘积

题目&#xff1a; 题解&#xff1a; class Solution {public int maxProduct(String[] words) {Map<Integer, Integer> map new HashMap<Integer, Integer>();int length words.length;for (int i 0; i < length; i) {int mask 0;String word words[i];in…