快速理解类的加载过程

当程序主动使用某个类时,如果该类还未加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:

1.加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象;

2.链接:Java类的二进制代码合并到JVM的运行状态之中的过程。

(1)验证:确保加载的类信息符合JVM规范,没有安全方面的问题;

(2)准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配;(static是在内初始化之前就完成了)

(3)解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。()

3.初始化:

(1)执行类构造器<clinit>()方法的过程:类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器);(JVM去完成的)

(2)当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化;

(3)虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

总结:

虚拟机把Class文件加载到内存,并对数据进行校验、转换解析和初始化,形成可以虚拟机直接使用的Java类型,即java.lang.Class,如下图:

接下来会对该三个步骤逐一进行解释。

一.类的加载

作用:查找和导入Class文件。

步骤:

1.通过一个类的全限定名获取定义此类的二进制字节流;(那么这个时候需要一个寻找器,来寻找获取我们的二进制字节流,而java中恰好有这么一段代码模块,可以实现通过类全名来获取此类的二进制字节流这个动作,并且将这个动作放到放到java虚拟机外部去实现,以便让应用程序决定如何获取所需要的类,实现这个动作的代码模块成为“类加载器”)

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

3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。(此时静态数据结构已经放进了方法区,但是此时我们没有办法去进行访问,java当中去访问数据的方法是通过引用去操作对象,然后通过对象去操作数据,所以还需要再堆当中去生成一个代表代表这个类的java.lang.Class对象,作为方法区中的数据访问入口)

在加载阶段完成之后,这个时候在内存当中,运行时数据区的方法区以及堆就已经有数据了:

(1)方法区:类信息,静态变量,常量;

(2)堆:代表被加载类的java.lang.Class对象。

及时编译之后的热点代码并不在这个阶段进入方法区。

类加载器:

类加载器作用是用来把类(class)装载进内存的,JVM 规范定义了如下类型的类的加载器:

二.类的链接

1.验证

作用:

验证只要是为了确保Class文件中的字节流包含的信息完全符合当前虚拟机的要求,并且还要求我们的信息不会危害虚拟机自身的安全,导致虚拟机的崩溃。

验证内容:

文件格式的验证、元数据验证、字节码的验证、符号引用的验证。

2.准备

作用:

为类的静态变量分配内存,并且初始化为当前类型的默认值。

解释:

1.这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化,这里不会为实例变量(也就是没加static)分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中;

2.进行分配内存的只是包括类变量(静态变量),而不包括实例变量,实例变量是在对象实例化时随着对象一起分配在java堆中的,通常情况下,初始值为零值,假设public static int a=1,那么a在准备阶段过后的初始值为0,不为1,这时候只是开辟了内存空间,并没有运行java代码,a赋值为1的指令是程序被编译后,存放于类构造器()方法之中,所以a被赋值为1是在初始化阶段才会执行。

3.解析

作用:

把类中的符号引用转换为直接引用:

(1)符号引用就是一组符号来描述目标,可以是任何字面量;

(2)直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

(因为再一个Class文件中,没有办法去表示引用关系,只能告诉你引用到10行或者20行这种,直接应用就表示你执行的位置在内存当中有一块具体的地址,比如说a指向b是符号应用,a指向b所在的位置0x01,在内存中这叫直接引用)

解释:

(1)解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行;

(2)直接应用是与虚拟机内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般是不相同的,如果有了直接引用,那引用的目标必定存在内存中;

(3)类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过VM垃圾回收机制可以回收这些Class对象。(同一符号引用进行多次解析请求是很常见的,除invokedynanic指令以外,虚拟机实现可以对第一次解析结果进行缓存,来避免解析动作重复进行,无论是否真正执行了多解析动作,虚拟机需要保证的是在同一个实体中,如果一个引用符号之前已经被成功解析过,那么后续的引用能析请求就应当一直成功,同样的,如果第一次解析失败,那么其他指令对这个符号的解析请求也应该收到相同的异常)

(4)inDy(invokedynamic)是java7引入的一条新的虚拟机指令,这是自 1.0 以来第一次引入新的虚拟机指令,到了 java 8 这条指令才第一次在java 应用,用在 lambda 表达式中,indy 与其他 invoke 指令不同的是它允许由应用级的代码来决定方法解析。

三.类的初始化

作用:

初始化阶段是执行类构造器Clinit()方法的过程,或者讲得通俗易懂些,加载和链接以外的工作都需要在初始化中去完成。

在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据自己通过程序制定的主观计划去初始化变量和其他资源,比如赋值。

什么时候发生类初始化:

1.类的主动引用(一定会发生类的初始化):

(1)当虚拟机启动,先初始化main方法所在的类;

(2)new一个类的对象;

(3)调用类的静态成员(除了final常量)和静态方法;

(4)使用java.lang.reflect包的方法对类进行反射调用;

(5)当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类;

2.类的被动引用(不会发生类的初始化):

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

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

(3)引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)。

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

1.声明类变量是指定初始值;

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

按照程序逻辑,必须把静态变量定义在静态代码块的前面,因为两个的执行是会根据代码编写的顺序来决定的,顺序搞错了可能会影响你的业务代码。

JVM初始化步骤:

1.假如这个类还没有被加载和连接,则程序先加载并链接该类;

2.假如该类的直接父类还没有被初始化,则先初始化其直接父类;

3.假如类中有初始化语句,则系统依次执行这些初始化语句。

四.举例

代码如下:

public class Test {public static void main(String[] args) {A a = new A();System.out.println(A.m);/*1.加载到内存 ,会产生一个类对应class对象2.链接 ,链接结束后 m = 03.初始化 m= 100:<clinit>(){System.out.println("A类静态代码块初始化”);m = 300;m=100}*/}
}class A {static {System.out.println("A类静态代码块初始化");m = 300;}static int m = 100;public A() {System.out.println("A类的无参构造初始化");}
}

输出结果:

A类静态代码块初始化
A类的无参构造初始化
100

 大概流程如下:

首先在方法区产生了一些该类的静态数据,然后在加载的类的时候,就产生了对应的class,然后看main()方法,main()方法后就开始链接,此时m有一个初始值0,等链接完没有问题的时候,就开始执行代码,此时new A(),就产生了一个A类的对象,这个对象就会去找到自己的Class类(指向),通过A类的数据结构,给A类对象赋值(拿到数据),赋值完成后,通过clinit()方法初始化数据,得到m等于100。

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

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

相关文章

宝塔-docker拉取宝塔镜像,并运行宝塔镜像

宝塔-拉取宝塔镜像&#xff0c;并运行镜像 第1步&#xff1a;查询 docker search btpanel/baota此docker镜像由堡塔安全官方发布&#xff0c;镜像版本为宝塔面板9.2.0正式版和9.0.0_lts 稳定版&#xff0c;镜像会随着宝塔面板更新。 目前支持x86_64和arm架构可供下载使用 版本…

穷举vs暴搜vs深搜vs回溯vs剪枝专题一>子集

题目&#xff1a; 两个方法本质就是决策树的画法不同 方法一解析&#xff1a; 代码&#xff1a; class Solution {private List<List<Integer>> ret;//返回结果private List<Integer> path;//记录路径&#xff0c;注意返回现场public List<List<Int…

leecode双指针部分题目

leecode双指针部分题目 1. 验证回文串2. 判断子序列3. 两数之和 II - 输入有序数组4. 盛最多水的容器5. 三数之和 1. 验证回文串 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 …

TCP协议简单分析和握手挥手过程

TCP介绍 TCP是可靠的传输层协议&#xff0c;建立连接之前会经历3次握手的阶段。 确认机制&#xff1a;接受方 收到数据之后会向 发送方 回复ACK重传机制&#xff1a;发送方 在一定时间内没有收到 接收方的ACK就会重新发送 握手目的&#xff1a;与端口建立连接 TCP的三次握手 …

opencv所有常见函数

一、opencv图像操作 二、opencv图像的数值运算 三、opencv图像的放射变换 四、opencv空间域图像滤波 五、图像灰度化与直方图 六、形态学图像处理 七、阈值处理与边缘检测 八、轮廓和模式匹配

【Excel】单元格分列

目录 分列&#xff08;新手友好&#xff09; 1. 选中需要分列的单元格后&#xff0c;选择 【数据】选项卡下的【分列】功能。 2. 按照分列向导提示选择适合的分列方式。 3. 分好就是这个样子 智能分列&#xff08;进阶&#xff09; 高级分列 Tips&#xff1a; 新手推荐基…

【STM32练习】基于STM32的PM2.5环境监测系统

一.项目背景 最近为了完成老师交付的任务&#xff0c;遂重制了一下小项目用STM32做一个小型的环境监测系统。 项目整体示意框图如下&#xff1a; 二.器件选择 单片机&#xff08;STM32F103&#xff09;数字温湿度模块&#xff08;DHT11&#xff09;液晶显示模块&#xff08;0.8…

ReactPress最佳实践—搭建导航网站实战

Github项目地址&#xff1a;https://github.com/fecommunity/easy-blog 欢迎Star。 近期&#xff0c;阮一峰在科技爱好者周刊第 325 期中推荐了一款开源工具——ReactPress&#xff0c;ReactPress一个基于 Next.js 的博客和 CMS 系统&#xff0c;可查看 demo站点。&#xff08;…

2024,大模型杀进“决赛圈”

Henry Chesbrough在著作《通过技术创新盈利势在必行》中&#xff0c;曾提出过一个创新的“漏斗模型”。开放式创新一开始鼓励百花齐放&#xff0c;但最终只有10%的技术能够通过这个漏斗&#xff0c;成功抵达目标市场target market&#xff0c;进入到商业化与产业化的下一个阶段…

STM8单片机学习笔记·GPIO的片上外设寄存器

目录 前言 IC基本定义 三极管基础知识 单片机引脚电路作用 STM8GPIO工作模式 GPIO外设寄存器 寄存器含义用法 CR1&#xff1a;Control Register 1 CR2&#xff1a;Control Register 2 ODR&#xff1a;Output Data Register IDR&#xff1a;Input Data Register 赋值…

【CSS in Depth 2 精译_081】 13.1:CSS 渐变效果(下)——CSS 径向渐变(13.1.3)+ CSS 锥形渐变(13.1.4)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 13 章 渐变、阴影与混合模式】 ✔️ 13.1 渐变 ✔️ 13.1.1 使用多个颜色节点&#xff08;上&#xff09;13.1.2 颜色插值方法&#xff08;中&#xff09;13.1.3 径…

ubuntu 用 ss-tproxy的最终网络结构

1、包含了AD广告域名筛选 2、Ss-tproxy 国内国外地址分类 3、chinadns-ng解析 4、透明网关 更多细节看之前博客 ubuntu 用ss-TPROXY实现透明代理&#xff0c;基于TPROXY的透明TCP/UDP代理,在 Linux 2.6.28 后进入官方内核。ubuntu 用 ss-tproxy的内置 DNS 前挂上 AdGuardHome…

BUUCTF Pwn [HarekazeCTF2019]baby_rop2 题解

下载 得到两个文件 checksec 64位 拖入IDA64 查看main函数 看到给了个libc说明这题是ret2libc题 这里的打印函数是printf 所以利用printf函数的plt输出真实地址got 但printf的got好像不行 所以换成了read的got 因为这是64位程序 所以用寄存器传参&#xff1b;又因为printf得…

语音识别失败 chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限

环境&#xff1a; Win10专业版 谷歌浏览器 版本 131.0.6778.140&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; 问题描述&#xff1a; 局域网web语音识别出现识别失败 chrome控制台出现下获取浏览器录音功能&#xff0c;因为安全性问题&#xff0c;需要在…

【一本通】输入两个不同的数,通过指针对两个数进行相加和相乘

【一本通】输入两个不同的数&#xff0c;通过指针对两个数进行相加和相乘 C语言代码C代码Java代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入两个不同的数&#xff0c;通过指针对两个数进行相加和相乘&#xff0c;并输出。 输入 …

X.game解析柚子币提升速效双向利好和年中历史新低原因

柚子币最新消息&#xff0c;币安宣布将于2024年9月25日21:00左右暂停柚子币网络上的代币存取业务&#xff0c;以全力支持即将到来的柚子币网络升级和硬分叉&#xff0c;这一消息为柚子币的未来发展增添了新的期待和变数。 除了速度的提升&#xff0c;Spring1.0还带来了诸多技术…

redis集群安装部署 redis三主三从集群

redis集群安装部署 redis三主三从集群 1、下载redis2、安装redis集群 三主三从3、配置redis开机自启动3.1、建立启动脚本3.2、复制多份redis启动脚本给集群使用3.3、添加可执行权限3.4、配置开机自启动 1、下载redis 本次redis安装部署选择当前最新的稳定版本7.4.1 下载链接: …

数据结构,链表的简单使用

任意位置删除&#xff1a; void Any_Del(LinkListPtr h,int a)//任意删 {if(NULLh||a>h->len){printf("删除失败");}LinkListPtr ph;for(int i0;i<a-1;i){pp->next;}LinkListPtr p2p;p2p2->next;p->nextp->next->next;free(p2);p2NULL;h-&g…

Servlet容器来扫描指定包中的类 找到带有WebServlet注解的类

项目框架如上图 myweb下边三个类 package com.qcby.tomcat.myweb;import com.qcby.tomcat.webServlet.WebServlet;WebServlet(urlPatterns {"MyFirstServlet"}) public class MyFirstServlet {}package com.qcby.tomcat.myweb;import com.qcby.tomcat.webServlet.W…

两数之和(Hash表)

优质博文&#xff1a;IT-BLOG-CN 一、题目 给定一个整数数组nums和一个整数目标值target&#xff0c;请你在该数组中找出"和"为目标值target的那两个整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元…