【JVM】Java类的加载机制!

2023-09-13 19:31:36

一、类的生命周期

类加载过程包含:加载验证准备解析初始化 ,一共包括5 个阶段。

(1)加载:

简单来说就是将java类的字节码文件加载到机器内存中。在加载类时,Java虚拟机必须完成以下3件事情: 

  • 通过类的完全限定名称获取定义该类的二进制字节流。
  • 将该字节流表示的静态存储结构转换为Metaspace元空间区的运行时存储结构。
  • 在内存中生成一个代表该类的 Class 对象,作为元空间区中该类各种数据的访问入口。

(2)验证:

确保 Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

(3)准备:

为类的静态分配内存,并将其初始化为默认值当一个类验证通过时,虚拟机就会进入准备阶段。在这个阶段,虚拟机就会为这个类分配相应的内存空间,并设置默认初始值。Java虚拟机为各类型变量默认的初始值如表所示。

类型

默认初始值

byte

0

short

0

int

0

long

0L

float

0.0f

double

0.0

char

\u0000

boolean

false

reference

null

没有对static fianl修饰的变量进行赋值,因为final修饰的变量已经在编译阶段就进行了赋默认值,而在准备阶段进行的是显式赋值 

(4)解析:

将常量池的符号引用替换为直接引用的过程。其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。

符号引用就是一些自变量的引用,和虚拟机的内部数据结构和和内存布局无关。比较容易理解的就是在Class类文件中,通过常量池进行了大量的符号引用。但是在程序实际运行时,只有符号引用是不够的,比如当如下println()方法被调用时,系统需要明确知道该方法的位置。

(5)初始化:

初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 <clinit>()方法的过程

对于<clinit>()方法的调用,也就是类的初始化,虚拟机会在内部确保其多线程环境中的安全性。

虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。

正是因为函数<clinit>()带,因此,如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个线程阻塞,引发死锁。并且这种死锁是很难发现的,因为看起来它们并没有可用的锁信息。

如果之前的线程成功加载了类,则等在队列中的线程就没有机会再执行<clinit>()方法了。那么,当需要使用这个类时,虚拟机会直接返回给它已经准备好的信息。

二、类加载的主动引用和被动引用 

(1)主动引用(会发生初始化)

  1.  遇到new、getstatic、putstatic、invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
  3. 加载一个类,如果其父类还未加载,则先触发该父类的加载。
  4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main() 方法的类),虚拟机会先加载这个类。
  5. 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了加载,则该接口要在实现类之前被加载。

(2)被动引用(看上去会,其实不会发生初始化):

  1. 通过子类引用父类的静态字段,不会导致子类初始化
  2. 通过数组定义类引用类,不会触发此类的初始化
  3. 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

三、类加载器

类加载器是Java虚拟机中的一个子系统,用于将类的字节码文件加载到内存中,并转换为Java的类对象。Java虚拟机需要根据类的全名来定位并加载类的字节码文件,而类加载器负责从文件系统、网络等位置查找类文件,并加载到Java虚拟机中。

Java虚拟机中有三种类加载器,分别是启动类加载器、扩展类加载器和应用程序类加载器。

(1)Bootstrap ClassLoader:启动加载器

它负责加载Java的核心类(如String、System等)。它比较特殊,因为它是由原生C++代码实现的,并不是java.lang.ClassLoader的子类。启动类加载器无法被 Java 程序直接引用,所以下面的运行结果为null:

(2)Extension ClassLoader:扩展类加载器

它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext)中JAR包的类,我们可以通过把自己开发的类打包成JAR文件放入扩展目录来为Java扩展核心类以外的新功能。

(3)System ClassLoader(或Application ClassLoader):应用程序(系统)类加载器

它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader来获取系统类加载器:

四. 对象的创建过程

Step1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

Step2:分配内存

类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。内存分配的查找方式“指针碰撞”“空闲列表” 两种。

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

Step5:执行 init 构造方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java 程序的视角来看,对象创建才刚开始,<init> 构造方法还没有执行,目前所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 构造方法,把对象按照程序逻辑的意愿进行初始化,这样一个真正可用的对象才算完整创建出来。

更多推荐

低代码系统哪里好

低代码作为近些年来被热议的话题,一直备受争议。低代码的出现更多的是用来辅助那些没有太多技能的人士而使用,在某些方面依然需要强大的代码来解决生产革新。所以低代码也不是浑水猛兽,也需要根据实际情况加以利用。那么为什么低代码会收到如此的争议呢?数聚将从多个角度来为企业主做揭秘。首先,什么是低代码系统呢?低代码系统是一种通过封

线程池概念讲解

目录一:基本概念二:为什么要使用线程池三:使用线程池有哪些优势四:线程池应用场景介绍一:基本概念线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象二:为

Dubbo常考知识点

Dubbo常考知识点Dubbo⽀持哪些负载均衡策略Dubbo是如何完成服务导出的?Dubbo是如何完成服务引⼊的?Dubbo的架构设计是怎样的?Dubbo⽀持哪些负载均衡策略随机:从多个服务提供者随机选择⼀个来处理本次请求,调⽤量越⼤则分布越均匀,并⽀持按权重设置随机概率轮询:依次选择服务提供者来处理请求,并⽀持按权重

彩虹医疗器械彩超、内窥镜维修技术学习

近几年随着医疗行业的快速发展,医疗器械的需求量不断增加,同时对医疗器械的维修和保养需求也在不断增长随着医疗技术的不断进步,新型、复杂的医疗器械不断涌现,这对维修技术提出了更高的要求。加强技术研发是必经之路,通过加强工程师培训、提高工程师工作效率和质量等方式来提升公司的整体竞争力。设备维修人员要注重专业素养和技能水平的提

C++day7

仿照vector手动实现自己的myVector,实现二倍扩容功能#include<iostream>usingnamespacestd;template<typenameT>classmy_vector{intsize;//可存储的容量大小intnum;//当前存储的元素个数T*data;//存储数据的空间地址publ

Pytorch从零开始实战03

Pytorch从零开始实战——天气识别本系列来源于365天深度学习训练营原作者K同学文章目录Pytorch从零开始实战——天气识别环境准备数据集模型选择模型训练数据可视化总结环境准备本文基于Jupyternotebook,使用Python3.8,Pytorch2.0.1+cu118,torchvision0.15.2,

虚幻4学习笔记(12)操控导入的角色、动画蓝图、播放蒙太奇和打包、角色重定向

虚幻4学习笔记操控导入的角色设置鼠标旋转关掉动态模糊动画蓝图、播放蒙太奇和打包角色走路奔跑动画shift奔跑F跳舞移动打断跳舞打包角色重定向姿势调整解决跑步腿分太开隐藏剑B站UP谌嘉诚课程:https://www.bilibili.com/video/BV164411Y732操控导入的角色新建项目导入角色模型FBX保存

JavaScript -【第四周】

文章来源于网上收集和自己原创,若侵害到您的权利,请您及时联系并删除数组知道什么是数组及其应用的场景,掌握数组声明及访问的语法。1.数组是什么?数组:(Array)是一种可以按顺序保存数据的数据类型【为什么要数组】:思考:如果我想保存一个班里所有同学的姓名怎么办?使用场景:如果有多个数据可以用数组保存起来,然后放到一个变

神经网络-pytorch版本

pytorch神经网络基础torch简介torch和numpyimporttorchimportnumpyasnpnp_data=np.arange(6).reshape((2,3))torch_data=torch.from_numpy(np_data)tensor2array=torch_data.numpy()p

openssl创建CA证书教程

配置生成CA证书总示意图:(1),通过openssl创建CA证书第一步:创建一个秘钥,这个便是CA证书的根本,之后所有的东西都来自这个秘钥#通过rsa算法生成2048位长度的秘钥opensslgenrsa-outmyCA.key2048第二步:是通过秘钥加密机构信息形成公钥#公钥包含了机构信息,在输入下面的指令之后会有

4G工业路由器,开启智能工厂,这就是关键所在

​提到工业物联网,首先联想到的就是数据传输。要把海量的工业数据从设备端传到控制中心,无线数传终端就发挥着重要作用。今天就跟着小编来看看它的“联”是怎么建立的吧!原文:https://www.key-iot.com/iotlist/1838.html一提到无线数传终端,相信大家首先想到的是DTU模块。其实按照功能定位,又

热文推荐