JVM详解:类的加载过程

JVM中类的加载主要分为三个部分,分别为加载(loading),链接(linking),初始化(initing)。其中加载负责的主要是讲类文件加载到内存中变为类对象,不过此时只有基本结构,没有实际数据。链接阶段还会被细分为验证,准备和解析,其中验证时对字节码文件进行格式校验(实际上所有阶段在执行前都会对自己需要的数据进行校验),准备阶段则是对静态属性进行默认赋值以及类型校验,而解析阶段则是这个环节最重要的,主要是将加载阶段没有加载的结构进行加载,这就相当于加载阶段是加载了一个和简单的骨架,而链接阶段则是将剩余的骨架补齐。最终的初始化阶段则是最终的赋值,为这具骨头附加血肉,这三个阶段的具体实现如下:

1. 加载(loading)

JVM通过加载java服务的启动类来开启运行java服务,任何非启动类的加载都是由另一个类的的的调用,触发加载的。一个类调用另一个类的逻辑在编译后的字节码文件中的体现是符号引用,当需要使用那个类时,才会通过符号引用,将其加载进内存中的。不过此时内存中保存的对象信息,包括方法属性常量等等,都只是符号引用,并不是真实引用。

那么什么是符号引用呢?具体我也不清楚,但可以知道的是,在没加载类或方法之前,其是保存在字节码文件中,而符号引用其中保存了可以找到目标类或方法的字节码文件的信息(不仅仅是这个作用,符号饮用记录了很多类或方法等,其表示的东西的信息)。

1.1 加载前的检查

JVM的类加载前需要进行大量的检查,大致包括如下内容:

  • 判断是否已经加载过这类了,如果出现类名相同,内容不同的情况,直接报错LinkageError。
  • 检查类文件格式是否正确,可能会抛出ClassFormatError(文件格式不正确)和ClassFormatError(版本不对)的错误。
  • 检查找到的类文件和符号饮用的描述是否一致,如果不是目标类,则报错NoClassDefFoundError。
  • 加载其父类的符号引用,如果父类是接口(IncompatibleClassChangeError)或自身(ClassCircularityError),则会报错。

一旦检查通过,就会将当前类记录在其对应的类加载器中,表示类已经加载过了。

类加载器:见名知意,就是讲类加载进内存的东西,不过随着类的分布位置不同,也需要不同的类加载器,JVM内置了三种类加载器,其中包括:

  • 引导类加载器(Bootstrap ClassLoader):这是 JVM 中最底层的类加载器,负责加载 Java 核心库(如 java.lang.*java.util.* 等),这些类位于 JDK 中的 jre/lib/rt.jar 或类似的系统路径下。它由 JVM 提供并且是不可见的,无法直接实例化。
  • 扩展类加载器(Extension ClassLoader):负责加载 JDK 扩展库(即 jre/lib/ext 目录下的类,或者 java.ext.dirs 配置的路径下的类)。它也是由 JVM 提供的,但与引导类加载器不同,它加载的是 Java 扩展库。
  • 应用类加载器(Application ClassLoader):负责加载用户应用程序的类路径(classpath)中的类,通常就是你在命令行中通过 java -cpjava -classpath 指定的类路径。这是最常用的类加载器,用于加载 Java 程序中定义的类。

但是创建这三个类加载器的目的是什么呢?为什么不直接把类放在一起加载呢?

首先这一定和性能无关,因为我与其使用三种加载器,不如将一种加载器复制三份。那么作用就一定是功能性的了,大概率是为了解藕不同功能的类,防止在一起互相影响,引发不可预料的事件。

除了JVM内置的加载器外,JVM还允许开发者自定义加载器,这样就可以让开发者自己处理一些刁钻的类的位置,比如在另一个服务器,或数据库中的类等等。

1.2 类的加载过程

JVM类的加载过程,大概可以将类分为两种,一种是数组类,一种是实例类(也就是非数组类)。

实例类

加载实例类时,JVM会检查类是否加载过,如果加载过,则认为其已经在内存中,跳过加载过程,如果不在,则将符号引用交给类加载器,让其寻找并加载对应的类。

当类加载或解析失败时,类加载器会抛出LinkageError或其子类的实例,当第一次加载,没有找到类的字节码文件时,则会跑出LinkageError的子类ClassNotFoundException的实例。

同样的NoClassDefFoundError也是LinkageError的子类实例,不同的是其通常是类在某个时刻加载过,但后续没有找到。JVM规定两次查找相同的类必须返回同一类对象,所以也可以说NoClassDefFoundError是在JVM的堆中没有找到类对象,而ClassNotFoundException是在磁盘中没有找到类的字节码文件。

数组类

而对于数组类,其没有二进制表示,是由JVM拿到相应参数来创建的。JVM会优先检查数组类内部元素是否为引用类型,如果是,其类是否在之前加载过,如果没有则会先加载器内部元素的类(如果元素的类还包含其他的类,则递归这一过程)。最终在由JVM创建数组类。如果不是,该数组类会被标记为引到类加载器加载的类(防止下一次重复检查,直接加载即可)。

1.3 类的加载约束

假设两个类加载器,加载了两个同名内容却不同的类,由于JVM规定一个类只允许加载一次,当一个类加载完成后,另一个加载时会被当成已经加载过了,那么在使用这类时,就会出现类的实际行为与预期不一致的问题。

为了防止这种情况的发生,JVM采用了一种名为类加载约束的方式。JVM要求加载类之前,如果发现了同名的类,必须保证其符号引用中记录的类格式一样(符号饮用的另一个作用,在编译期间生成的符号饮用会记录类的格式)。如果不同,则会返回LinkageError的错误。

实际上述情况还有另一种说明,就是根本不会发生类加载前的格式检查,而是同名类会直接拒绝加载,使用内存中的,如果与预期的类不一致,则会直接抛出LinkageError的错误,具体实现会在不同版本的JVM中有所差异,不过归根到底都是遇到同类名不同内容时,会返回错误。

2. 连接(linking)

连接分为三个阶段,分别是验证,准备,以及解析

2.1 验证

在验证阶段,JVM会对文件再次进行检查,检查二进制文件(字节码文件)是否符合预期,如果不符合则会抛出异常LinkageError或其子类。

2.2 准备

在准备阶段,JVM会对加载类中的静态字段,并赋予其默认值(不是实际的值,实际的值会在解析阶段使其赋值)。并且还会对其实现或重写的接口或父类方法之前的类型进行对比检查(这会导致接口和父类的提前加载)。

2.3 解析

在类的加载阶段(loading),大量类的父类、字段、方法、接口等被作为符号引用,存在常量池中。而在linking的解析阶段,这些符号引用将被转换为真实引用,并将方法和属性加载到方法区中。但是对于反射和动态方法,这些无法在编译及加载期间确定其具体值的,必须在实际执行时,才能够进行符号引用到实际引用的变换操作。

JVM在解析阶段对方法和字段进行解析时,如果当前类寻找不到,则会在当前类的继承链或实现接口进行查找。如果还是没有找到,则会抛出错误。

在当前被加载的类中,未被使用的方法里保存的类,会被作为符号引用存在常量池中,不会被解析为实际引用,。不过正在加载类的整个继承链和接口,都会从子类的linking阶段的准备阶段开始初始化,并且需要在子类初始化完成之前初始化完成。父类和接口虽然需要提前被初始化完成,不过这也并不代表优先于子类初始化,而是会卡在连接阶段,只有在子类初始化时,父类和接口等才会被递归初始化,这些后面初始化阶段会说。

3. 初始化

由于JVM是多线程的,并且规定类不能被加载多个,所以又可能会出现多个线程使用一个类创建实例,或递归请求初始化某个类或接口的情况。为了解决这个问题,JVM使用了初始化锁来确保线程间初始化的安全。

在当前线程得到初始化锁后,JVM会首先会初始化全部的常量和静态变量(连接期间赋予的是默认值,现在才会赋予真实值),然后判断自己的父类是否已经初始化(如果父类在之前使用过,就是已经初始化的状态),如果没有则开始对自己的父类或接口等进行递归初始化。

父类和初始化完毕后,此时会开始执行静态代码块的代码(从最高级的祖父类开始,逐渐向下级执行,因为子类的初始化完成,依赖于父类和接口的初始化完成)。静态代码一旦执行成功,那么就代表整个初始化环节结束,线程会释放初始化锁。

类初始化后,所有的方法都已经实际存在于内存中,此时还需要将本地代码(也就是其他语言的代码),和已经加载的方法进行绑定操作,让本地方法能够正常调用。到此一个类的加载就完成了。

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

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

相关文章

如何在CentOS 7上搭建SMB服务

如何在CentOS 7上搭建SMB服务 因项目测试需求,需要自行搭建SMB服务,**SMB(Server Message Block)**协议是一种常用的文件共享方式,它可以让不同操作系统之间共享文件、打印机等资源。本文将带你一步步搭建一个简单的S…

使用CNN进行验证码识别:深度学习与图像预处理教程

验证码(CAPTCHA)广泛用于区分人类和自动化程序(如机器人),通常由扭曲的字母、数字或符号组成。为了实现验证码的自动识别,深度学习尤其是卷积神经网络(CNN)非常有效。本文将带你一起…

STM32 51单片机设计半导体制冷片温控设计

目录 前言 一、本设计主要实现哪些很“开门”功能? 二、电路设计原理图 1.电路图采用Altium Designer进行设计: 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 基于STM32与51单片机的半导体制冷片温控设计 前言 随着现代工业、医疗…

ssm114基于SSM框架的网上拍卖系统的设计与实现+vue(论文+源码)_kaic

摘 要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势,商品拍卖当然也不能排除在外,随着商品拍卖管理的不断成熟,它彻底改变了过去传统的经营管理方式,不仅使商品…

算法每日双题精讲——滑动窗口(长度最小的子数组,无重复字符的最长子串)

🌟快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。 🌟 别再犹豫了!快来订阅我们的算法每日双题精讲专栏,一起踏上算法学习的精彩之旅吧!💪…

批量从Excel某一列中找到符合要求的值并提取其对应数据

本文介绍在Excel中,从某一列数据中找到与已知数据对应的字段,并提取这个字段对应数值的方法。 首先,来明确一下我们的需求。现在已知一个Excel数据,假设其中W列包含了上海市全部社区的名称,而其后的Y列则是这些社区对应…

MaxKB

docker 安装 MaxKB docker run -d --namemaxkb --restartalways -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages cr2.fit2cloud.com/1panel/maxkbdocker psCONTAINER ID IMAGE …

越南很火的slots游戏投放Google谷歌广告策略

越南很火的slots游戏投放Google谷歌广告策略 越南的slot游戏市场正在借助Google广告代投策略推动增长。随着智能手机的普及和互联网的普及,越南的游戏市场迅速增长,吸引了越来越多的投资者和开发者进入该市场。 在这个竞争激烈的市场中,广告…

现代无线通信接收机架构:超外差、零中频与低中频的比较分析

写在前面:本博客是对三种接收机架构的学习笔记,仅供个人学习记录使用。内容主要是上网查阅的资料,以及个人的一些理解。如有错误的地方请指出! 文章目录 一、通信机基本架构1、射频发射级的基本组成及完成功能2、射频接收级的基本…

探索MoviePy:Python视频编辑的瑞士军刀

文章目录 🎬 探索MoviePy:Python视频编辑的瑞士军刀第一部分:背景介绍第二部分:MoviePy是什么?第三部分:如何安装MoviePy?第四部分:MoviePy的基本函数使用方法1. 视频剪辑2. 视频拼接…

修改数据库和表的字符集

1、修改数据库字符集 mysql> show CHARACTER SET; 查看所有字符集 mysql> show create database wordpress; 查看数据库wordpress当前字符集mysql> alter database wordpress character set gbk; 将数据库wordpress字符集改为gb…

67页PDF |埃森哲_XX集团信息发展规划IT治理优化方案(限免下载)

一、前言 这份报告是埃森哲_XX集团信息发展规划IT治理优化方案,报告中详细阐述了XX集团如何优化IT治理结构以适应新的要求。报告还分析了集团管控模式的变化,提出了六大业务中心的差异化管控策略,并探讨了这些变化对IT治理模式的影响。报告进…

C# WPF FontDialog字体对话框,ColorDialog颜色对话框 引用

WPF 并没有内置FontDialog和ColorDialog,但可以通过引用 Windows Forms 的控件来实现字体和颜色选择对话框功能。FontDialog 允许用户选择字体、样式、大小等设置。 添加 Windows Forms的引用 项目工程:右键“引用”》“添加引用”》勾选System.Windows…

家政服务小程序,家政行业数字化发展下的优势

今年以来,家政市场需求持续增长,市场规模达到了万亿级别,家政服务行业成为了热门行业之一! 家政服务种类目前逐渐呈现了多样化,月嫂、保姆、做饭保洁、收纳、维修等家政种类不断出现,满足了居民日益增长的…

kubernetes简单入门实战

本章将介绍如何在kubernetes集群中部署一个nginx服务,并且能够对其访问 Namespace Namespace是k8s系统中一个非常重要的资源,它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下,k8s集群中的所有的Pod都是可以相…

2分钟在阿里云ECS控制台部署个人应用(图文示例)

作为一名程序员,我有大量的个人代码和应用托管在Github/Gitee这些代码仓库。当我想要部署这些代码到我的阿里云ECS服务器时,往往会很麻烦,主要问题有这些: 需要手动安装和配置git,过程非常繁琐。每次都需要登录到机器…

【Kafka】集成案例:与Spark大数据组件的协同应用

🐇明明跟你说过:个人主页 🏅个人专栏:《大数据前沿:技术与应用并进》🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、什么是kafka 2、Kafka 的主要特性 3、Kafka 的…

Docker 的安装与使用

Docker 的安装 Docker 是一个开源的商业产品,有两个版本:社区版(Community Edition,缩写为 CE)和企业版(Enterprise Edition,缩写为 EE)。 Docker CE 的安装请参考官方文档&#xf…

逐行加载 HTML 内容并实时显示效果:使用 wxPython 的实现

这篇博客中,我们将详细分析如何使用 wxPython 构建一个简单的桌面应用程序,用于逐行加载并显示 HTML 文件的内容,并在加载完成后通过浏览器组件呈现最终页面。通过该应用,我们可以体验到逐行加载 HTML 内容的视觉效果,…

【Linux】HTTP协议和HTTPS加密

文章目录 HTTP1、概念2、认识URL3、协议格式、请求方法和状态码4、HTTP请求和响应报头5、Cookie和Session HTTPS1、对称和非对称加密2、对称非对称加密安全分析3、证书 HTTP 1、概念 我们在应用层定制协议时,不建议直接发送结构体对象,因为在不同的环境…