当前位置: 首页 > news >正文

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

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

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

相关文章:

JVM详解:类的加载过程

JVM中类的加载主要分为三个部分,分别为加载(loading),链接(linking),初始化(initing)。其中加载负责的主要是讲类文件加载到内存中变为类对象,不过此时只有基…...

Python →爬虫实践

爬取研究中心的书目 现在&#xff0c;想要把如下网站中的书目信息爬取出来。 案例一 耶鲁 Publications | Yale Law School 分析网页&#xff0c;如下图所示&#xff0c;需要爬取的页面&#xff0c;标签信息是“<p>”&#xff0c;所以用 itemssoup.find_all("p&…...

Visitor 访问者模式

1)意图 表示一个作用于某对象结构中的各元素的操作。它允许在不改变各元素的类的前提下定义用于这些元素的新操作。 2)结构 访问者模式的结构图如图 7-48 所示。 其中: Visitor(访问者) 为该对象结构中ConcreteElement 的每一个类声明一个 Vsit 操作。该操作的名字和特征标识…...

Mac解压包安装MongoDB8并设置launchd自启动

记录一下在mac上安装mongodb8过程&#xff0c;本机是M3芯片所以下载m芯片的安装包&#xff0c;intel芯片的类似操作。 首先下载安装程序包。 # M芯片下载地址 https://fastdl.mongodb.org/osx/mongodb-macos-arm64-8.0.3.tgz # intel芯片下载地址 https://fastdl.mongodb.org…...

Springboot采用jasypt加密配置

目录 前言 一、Jasypt简介 二、运用场景 三、整合Jasypt 2.1.环境配置 2.2.添加依赖 2.3.添加Jasypt配置 2.4.编写加/解密工具类 2.5.自定义加密属性前缀和后缀 2.6.防止密码泄露措施 2.61.自定义加密器 2.6.2通过环境变量指定加密盐值 总结 前言 在以往的多数项目中&#xff0…...

加载shellcode

​​​​​​ #include <stdio.h>#include <windows.h>DWORD GetHash(const char* fun_name){ DWORD digest 0; while (*fun_name) { digest ((digest << 25) | (digest >> 7)); //循环右移 7 位 digest *fun_name; //累加…...

K8S如何基于Istio实现全链路HTTPS

K8S如何基于Istio实现全链路HTTPS Istio 简介Istio 是什么?为什么选择 Istio?Istio 的核心概念Service Mesh(服务网格)Data Plane(数据平面)Sidecar Mode(边车模式)Ambient Mode(环境模式)Control Plane(控制平面)Istio 的架构与组件Envoy ProxyIstiod其他组件Istio 的流量管…...

React Query在现代前端开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 React Query在现代前端开发中的应用 React Query在现代前端开发中的应用 React Query在现代前端开发中的应用 引言 React Query …...

【HAProxy09】企业级反向代理HAProxy高级功能之压缩功能与后端服务器健康性监测

HAProxy 高级功能 介绍 HAProxy 高级配置及实用案例 压缩功能 对响应给客户端的报文进行压缩&#xff0c;以节省网络带宽&#xff0c;但是会占用部分CPU性能 建议在后端服务器开启压缩功能&#xff0c;而非在HAProxy上开启压缩 注意&#xff1a;默认Ubuntu的包安装nginx开…...

PostgreSQL中表的数据量很大且索引过大时怎么办

在PostgreSQL中&#xff0c;当表的数据量很大且索引过大时&#xff0c;可能会导致性能问题。以下是一些优化索引和表数据的方法&#xff1a; 1. 评估和删除不必要的索引 识别未使用的索引&#xff1a;使用pg_stat_user_indexes和pg_index系统视图来查找未被使用的索引&#x…...

【QML】QML多线程应用(WorkerScript)

1. 实现功能 QML项目中&#xff0c;点击一个按键后&#xff0c;运行一段比较耗时的程序&#xff0c;此时ui线程会卡住。如何避免ui线程卡住。 2. 单线程&#xff08;会卡住&#xff09; 2.1 界面 2.2 现象 点击delay btn后&#xff0c;执行耗时函数&#xff08;TestJs.func…...

认证鉴权框架SpringSecurity-1--概念和原理篇

1、基本概念 Spring Security 是一个强大且高度可定制的框架&#xff0c;用于构建安全的 Java 应用程序。它是 Spring 生态系统的一部分&#xff0c;提供了全面的安全解决方案&#xff0c;包括认证、授权、CSRF防护、会话管理等功能。 2、认证、授权和鉴权 &#xff08;1&am…...

计算器上的MC、MR、M+、M—、CE是什么意思?

在计算器中&#xff0c; MC键叫做memory clear&#xff0c;中文 清除存储&#xff0c;是一个清除寄存器中存储数字的指令。 MS键叫做memory save&#xff0c;中文 存入存储。 而MR键&#xff0c;则是一个读取原先存储在寄存器中的数字的指令。 M键指将当前数值存入寄存器以…...

无人机飞手执照处处需要,森林、石油管道、电力巡检等各行业都需要

无人机飞手执照在多个行业中确实具有广泛的应用需求&#xff0c;包括森林、石油管道、电力巡检等领域。以下是对这些领域无人机飞手执照需求的具体分析&#xff1a; 一、森林领域 在森林领域&#xff0c;无人机飞手执照对于进行高效、准确的森林资源管理和监测至关重要。无人机…...

计算机网络——路由选择算法

路由算法 路由的计算都是以子网为单位计算的——找到从原子网到目标子网的路径 链路状态算法...

【前端】技术演进发展简史

一、前端 1、概述 1990 年&#xff0c;第一个web浏览器诞生&#xff0c;Tim 以超文本语言 HTML 为基础在 NeXT 电脑上发明了最原始的 Web 浏览器。 1991 年&#xff0c;WWW诞生&#xff0c;这标志着前端技术的开始。 前端&#xff08;Front-end&#xff09;和后端&#xff08;…...

深入解析贪心算法及其应用实例

标题&#xff1a;深入解析贪心算法及其应用实例 一、引言 贪心算法&#xff08;Greedy Algorithm&#xff09;是一类简单、直观的算法设计策略&#xff0c;广泛应用于优化问题中。其基本思想是每一步都选择当前状态下最优的选择&#xff0c;即在每一步做出局部最优的决策&…...

电子工牌独立双通道定向拾音方案(有视频演示)

现在一些行业的客服人员在面对客户都要求使用电子工牌分别记录客服和顾客的声音,我们利用双麦克风阵列双波束拾音的方案设计了一个电子工牌方案.可以有效分别记录客服和顾客的声音. 方案思路: 我们采用了一个双麦阵列波束拾音的模块A-59,此模块可以利用2个麦克风组成阵列进行双…...

举例理解LSM-Tree,LSM-Tree和B+Tree的比较

写操作 write1&#xff1a;WAL 把操作同步到磁盘中WAL做备份&#xff08;追加写、性能极高&#xff09; write2&#xff1a;Memtable 完成WAL后将(k,v)数据写入内存中的Memtable&#xff0c;Memtable的数据结构一般是跳表或者红黑树 内存内采用这种数据结构一方面支持内存…...

React Native 全栈开发实战班 - 核心组件与导航

在 React Native 中&#xff0c;组件是构建用户界面的基本单元。React Native 提供了丰富的内置组件&#xff0c;涵盖了从基础布局到复杂交互的各种需求。本章节将详细介绍常用的内置组件&#xff0c;并重点讲解列表与滚动视图的使用。 1. 常用内置组件详解 React Native 提供…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error

在前端开发中&#xff0c;JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作&#xff08;如 Promise、async/await 等&#xff09;&#xff0c;开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝&#xff08;r…...

uniapp 实现腾讯云IM群文件上传下载功能

UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中&#xff0c;群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS&#xff0c;在uniapp中实现&#xff1a; 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...