JVM 类的加载篇

我们都知道一个类从加载到卸载一共分为七个过程

加载 - 链接(验证 - 准备 - 解析) - 初始化 - 使用 - 卸载

下文我们将详细解析这些过程

谁需要加载?

在Java中数据类型分为基本数据类型和引用数据类型,基本数据类型由虚拟机预定义,引用数据类型则需要类的加载

1.加载/装载(loading)

装载的过程就是将Java字节码加载到机器内存中,并在内存中构建出Java类的模板class对象

简而言之,就是将二进制的数据加载进内存变成class实例

大概可以概括为三个问题

1.通过类的全名获取二进制数据流

2.解析二进制数据流为方法区数据结构/类模型

3.创建java.lang.Class实例,表示该模型,作为这个类数据的访问入口

什么是类模板?

所谓类模板,其实就是Java类在JVM内存中的一个快照,JVM在字节码文件中解析出的常量池,类字段,类方法等信息存储到一个类模板中,这样JVM在运行的时候就可以在模板中获取类的任何信息,可以进行任何操作

反射就基于这一基础,如果没有类模板,JVM在运行时也无法进行反射操作

注:类模型存在方法区中

二进制流有哪些获取方式?

对于类的二进制文件,虚拟机有很多种获取方式

1.class文件

2.jar包,zip包等

3.数据库中的类的二进制文件

4.通过网络HTTP协议进行加载

在虚拟机获取到类的二进制信息之后就会加载一个Class实例,但是如果二进制文件不满足要求,则会抛出异常

Class文件在哪?

.class文件加载到元空间之后就会在堆区创建一个Class对象,用来封装类的数据结构,这个class对象是加载类的过程中创建的,每个类都对应有class对象

这样外界就可以直接通过访问CLass对象来获取class的数据结构了

特殊:数组类的加载

数组类本身不是由类的加载器负责创建而是JVM运行的时候根据徐区域直接创建的,但是数组元素类型仍然需要依赖类的加载器去创建 ,如果这里数组的元素是引用类型,那么就需要正常递归加载元素类型

2.链接(linking)

1.验证 

这里的需求就是验证加载进虚拟机的class文件是否符合java虚拟机规范,大部分要做如下检查

注:其中格式验证会在装载阶段一并执行,验证通过后才会将二进制数据加载到方法区(元空间)

说明:

1.格式验证  看开头魔数是不是0xCAFEBABE版本号是否支持等

2.语义检查,比如是否final修饰的方法或者类被重写或者继承了

3.字节码验证 比如看函数的调用是否指向了正确的类型参数,变量的赋值是否是正确的类型等

注:通过字节码验证也不能说明这个类完全没问题

4.符号引用验证         看符号引用是都能在常量池中找到对应的直接引用

2.准备阶段

这个阶段会为类的静态变量进行分配内存,这个阶段虚拟机会为类进行分配空间,并设置默认的初始值,比如int 赋值为0  long类型赋值为0L一样

注:1.Java不支持boolean类型,对于boolean类型,内部实现是int,int的初始化值为0,对应的boolean默认值就是false

2.修饰为static final的,在编译的时候就会分配,准备阶段会显示赋值

3.这里不会为实例变量进行初始化,实例变量会随着对象一起分配到堆中

4.这个阶段不会有代码被执行

3.解析阶段

简而言之就是将类,方法,接口,字段的符号引用转化为直接引用

Java虚拟机为每个类都准备了一个方法表,所有的方法都列在表里,当需要调用一个类的方法的时候,通过解析将符号引用转化为直接引用就可以得到目标方法在方法表的位置,从而调用方法.

注:解析就是将符号引用转化为直接引用,也就是得到类,字段等在内存中的引用或者是偏移量,可以说如果直接引用存在,系统中一定存在这样的类方法或者字段,符号引用则不能确定,但是虚拟机规范没有规定一定要按照顺序执行,HotSpot中解析就是在初始化之后再执行的

初始化(initiating)

这里的初始化其实就是对静态变量的值进行显示初始化了

类的初始化是类装载的最后一个阶段,到了这里才开始真正意义上执行了Java程序代码

重要方法:<clinit>()    有静态变量才有的方法

这个方法是Java编译器生成并且由JVM调用的,我们无法自定义重名方法,也不能调用这个方法

这个方法主要就是给类的static变量显示赋值和在静态代码块中赋值了

<init>方法,一定会出现在class表中的method中,涉及显示赋值,代码块和构造方法

先加载父类再加载子类?

加载一个类的时候虚拟机总是先加载他的父类,因为父类的clinit总在子类之前被调用,这也就说明了为什么父类的静态代码块要优先于子类了

哪些类没有clinit方法?

1.没有类变量和静态代码块的类

2.一个类中声明类变量但是没有使用初始化语句和静态代码块赋值操作

3.一个类中包含static final 修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式

clinit会产生死锁嘛?

类的初始化调用这个方法的时候虚拟机会保证其在多线程环境下正确的被加载同步,如果多个线程去初始化同一个类,只会有一个线程去执行这个clinit方法,正是因为这个方法是线程安全的,所以如果在一个类的clinit方法中有很耗时的操作,那么就可能造成多个线程阻塞的操作,从而引发死锁

主动使用 VS 被动使用

主动使用:Class只有在首次使用的时候才会被装载,java虚拟机不会无条件的装载Class类,Java虚拟机规定,一个类或者是接口在初次使用的时候一定要初始化,这里的使用就是主动使用

主动使用(默认这里加载,验证,准备已经完成)

1.创造类的实例的时候(包括反射,克隆,反序列化)

2.调用类的静态方法的时候

3.使用接口类的静态字段的时候

4.初始化子类发现父类没初始化,就先触发父类的初始化

5.虚拟机启动时,主类main()需要初始化

等等

被动使用:不会引起类的初始化

1.访问静态字段时,只有真正声明这个字段的类会初始化

2.使用数组定义类应用不会出现初始化

3.loadClass()方法记载一个类,也不会导致初始化

被动使用不会进行clinit方法的调用

类的使用

一旦这个类经过了装载,验证,准备,解析,初始化五个阶段,这时候就可以给开发者使用了,开发人员可以在程序中调用它的静态类成员信息,或者使用new关键字创建对象实例了.

类的卸载

我们先展示一个图

这个图表示了一个对象被创建出来之后,默认指向它的引用,这里其他的引用都好去除,就是类的加载器这个引用无法去除,如果是三个系统自带的类加载器,那么无法删除这个引用,这也就说明了为啥JDK8要将永久代变成元空间使用本地内存了,因为类几乎是无法卸载的,除非使用自定义的类的加载器,这才有可能将这个引用删除,从而解决类的卸载问题

类的加载器

类的加载器是JVM执行类加载机制的前提

ClassLoader作为Java的核心组件,所有的Class都是由ClassLoader进行加载的,它负责将class的二进制文件读进JVM内部,然后转化为Class实例,这里的classloader主要是在装载的阶段起作用

显示加载: 指的是在代码中调用ClassLoader来加载class对象如直接写Class.forName(name)

隐式加载:通过JVM自动进行加载,只要class文件中引用到了另一个类的对象,就会自动加载到内存中

加载的类是唯一的吗?

什么叫唯一:由两方面决定,加载器和类本身 比较两个类是否相等,只有这两个都相同才是相等的,不然即使是同一份class文件,被同一个虚拟机加载,只要加载的类加载器不同,那么这个两个类也一定不同    所以加载的类不是唯一的

双亲委派模型

定义:如果一个类加载器在接收到加载类的请求的时候,首先不会自己尝试加载这个类,而是把这个请求任务委托给父类加载器去完成,依此递归,如果父类加载器能完成就完成,完成不了再由子类加载器来完成

本质:规定了加载顺序 引导类先加载,拓展类其次,系统类最后,再是自定义类加载器

我们再说说三个JVM自带的加载器加载哪些内容吧

引导类加载器:加载JVM需要的类

扩展类加载器:记载标准扩展的类

系统类加载器:加载path下指定的类以及上放没有包含的类

源码分析

双亲委派机制在java.lang.ClassLoader.loadClass(String boolean)接口中体现,逻辑如下

1.在加载器的缓存查找有无目标类,有就直接返回

2.看父加载器是否为空,不为空则调用父加载器的接口进行加载

3.如果父加载器为空,则调用引导类加载器进行加载

4.以上都无法加载,就调用Classloader接口中的defineClass系列的native接口加载目标Java类

注:这里不要去想重写loadclass方法来打破双亲委派机制,因为不管是什么类加载其最后都会执行predeDineClass接口,这就是堆核心JDK库的保护

双亲委派机制的优势和劣势

双亲委派机制优势

1.避免了类的重复加载

2.保护核心api的安全

缺点

检查类是否加载的委托过程是单向的,这个方式虽然架构清晰,职责明确,但是顶层的加载器就不能访问底层的加载器所加载的类了,通常情况下,启动类加载器的类称为系统核心类,包括重要的系统接口,应用类访问启动类加载器加载的类自然没问题,但是启动类加载器访问不了应用类加载器加载的类,比如系统类加载器提供一个接口在应用类得以实现,该接口绑定一个工厂方法,用于创建实例,而接口和工厂方法都在启动类加载器中,此时就会出现工厂方法无法创建启动类加载器加载的应用实例的问题

破坏双亲委派机制的举例

1.由于类加载器是在jdk1.0的时候引入的,而jdk1.2才引入双亲委派模型,设计者不得不做出妥协,为了兼容这些代码,无法再以技术手段避免loadClass()被子类覆盖的可能性,只能在lang.ClassLoader中增加一个新的方法findCLass,引导用户尽可能去重写这个方法,按照loadClass的逻辑,在父类加载失败的时候就会来使用这个findCLass完成加载

2.线程上下文加载器

这是因为模型本身的缺陷导致的

如果有基础类型要回调用户的代码怎么办呢

典型的就是JNDI服务,在JDK1.3引入,目的是堆资源进行查找和集中管理,但是他是由启动类加载器进行加载的,需要调用其他厂商实现的SPI(服务提供者接口),通常把核心类提供外部服务并可以由应用层实现的接口称之为SPI

这里线程上下文加载器就要出手了,这个类加载器可以通过Thread类setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器,这时候问题也就迎刃而解了.

3.代码热部署/模块热替换

这就是由用户追求程序动态性导致的

IBM公司主导的视线模块而部署的关键是自定义的类加载机制的实现,每个程序都实现一个自己的类加载器,需要更换的时候直接将加载器一起替换,这里就不遵循双亲委派机制的树状结构,而是进一步发展成网状结构

'

热替换:这里就是不停止服务,止痛膏替换程序文件来修改程序的行为,关键在于服务不能中断,修改也必须立即表现扎起正在运行的系统上

市面上大部分脚本语言是支持热替换的,但是java不是天生支持的,所以只能使用ClassLoader了

注:不同ClassLoader即使加载两个相同的类,也是会认为是不同的类型的,所以可以实现,基本思路如下图

TomCat类加载机制

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

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

相关文章

Docker入门二(应用部署、迁移与备份)

文章目录 一、应用部署1.MySQL部署2.Redis部署3.Nginx部署 二、迁移与备份1.容器做成镜像2.把镜像被分成压缩包 一、应用部署 1.MySQL部署 在dokcer中部署mysql&#xff0c;以后不需要在宿主机上装mysql1.做端口映射docker run -id --namemysql5.7 -p 3306:3306 -e MYSQL_ROOT…

Fundamentals of Amazon MSK (Amazon Managed Streaming for kafka)

Amazon Managed Streaming for Apache Kafka 或 Amazon MSK 允许您在 AWS 中运行利用 Apache Kafka 的应用程序。 Kafka 提供了一个流处理平台&#xff0c;并作为基于发布者/订阅者的持久消息传递系统运行。 其主要功能是能够以极高的容错能力获取数据&#xff0c;允许这些记录…

网工内推 | 国企、上市公司网工、运维,CCNA即可,补贴福利多

01 深圳新思 招聘岗位&#xff1a;网络工程师&#xff08;中电集团&#xff09; 职责描述&#xff1a; 1&#xff1a;负责办公室电脑的桌面运维&#xff0c;主要是windows维护与应用维护&#xff1b; 2&#xff1a;负责办公室网络设备配置&#xff0c;如防火墙&#xff0c;交换…

CMake 编译 raylib 程序

CMakeLists.txt 内容如下&#xff1a; cmake_minimum_required(VERSION 3.0) project(t001) # 搜索指定目录下源文件 file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) # 包含头文件路径 include_directories(F:/vclib/raylib-5.0_win64_mingw-w64/include) # 包含静态…

CountDownLatch介绍和使用

1. CountDownLatch是什么 CountDownLatch 是 Java.util.concurrent 包中的一个同步工具类&#xff0c;用于控制线程的执行顺序。它的主要作用是让一个或多个线程等待其他线程完成操作后再继续执行。 2. CountDownLatch 类常用方法 CountDownLatch(int count) 是 CountDownLa…

使用maven命令行。手动搭建maven项目

要使用Maven命令行&#xff0c;首先需要确保已经安装了Maven。接下来&#xff0c;可以按照以下步骤手动搭建Maven项目&#xff1a; 创建项目目录&#xff1a;在命令行中切换到你希望创建项目的目录下&#xff0c;然后执行以下命令&#xff1a; mkdir myproject cd myproject初…

软件测试基础概念

一、需求 定义&#xff1a;满足用户期望或正式规定文档所需条件和技能&#xff0c;包含用户需求 用户需求&#xff1a;用户使用产品所必须完成的任务 该需求比较简略 软件需求&#xff1a;详细描述开发人员必须实现的软件功能 需求是标准&#xff0c;测试人员按照这个标准测…

python之第三方模块

配置pip源 更新pip pip install --upgrade pip 下载清华源 pip config set global.index-url Simple Index requests 用代码模拟向浏览器发送请求 #返回的是json格式&#xff0c;分页查询 import requests for i in range(0,100,10):resrequests.get(url"https://mov…

全球首个 AI 超级工程师:拥有全栈技能,一个指令就能完成整个开发过程

全球首位AI软件工程师Devin是由初创公司Cognition推出的&#xff0c;它被认为是世界上第一个完全自主的AI软件工程师[2][15]。Devin具备强大的编程和软件开发能力&#xff0c;能够在多个方面协助或完全独立地完成软件开发任务[15]。它的核心能力包括自学新语言、开发迭代App、自…

基于qt和css的MP3音乐播放器引擎开发

1 QMainWindow&#xff1a; QMainWindow 是用于创建应用程序主窗口的类。它通常用于具有菜单栏、工具栏、状态栏等标准组件的窗口。 QMainWindow 提供了一种框架&#xff0c;用于组织和管理应用程序的用户界面元素。它可以包含其他小部件&#xff08;widgets&#xff09;和布局…

C语言分析基础排序算法——归并排序

目录 归并排序 递归版本 非递归版本 非递归版本的问题 归并排序小优化 归并排序 归并排序&#xff0c;分为分治以及合并&#xff0c;分治部分可以使用递归或者非递归完成&#xff0c;归并排序的基本思路是&#xff1a;将已有序的子序列合并&#xff0c;得到完全有序的序列…

解决 :nvrtc: error: invalid value for --gpu-architecture (-arch)

核心&#xff1a;在显卡安装的cuda版本适配的pytorch中&#xff0c;更换pytorch的版本 刚遇到这个错误时&#xff0c;在网上搜索了一下&#xff0c;感谢博主1和博主2的解决方法带给我的启发。 标题服务器cuda是11.3版本&#xff0c;配置其他环境“御用”的pytorch安装语句 co…

社区维修平台|基于SpringBoot+ Mysql+Java+JSP技术的社区维修平台设计与实现(可运行源码+数据库+设计文档+部署说明+视频演示)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 住户后台功能 维修员前台功能 维修员后台功能 管理员功能登录 系统功能设计 数据库E…

用python实现Dubins曲线生成

Dubins曲线是连接两个具有指定方向和位置的点的最短路径&#xff0c;其中路径受到固定曲率约束&#xff08;如车辆的转向限制&#xff09;。Dubins曲线常用于机器人路径规划、车辆轨迹规划等领域。 Dubins曲线可以分为三种类型&#xff1a;CCC (Curve-Curve-Curve), CCL (Curv…

C++面试题和笔试题(四)

一、intx[6][4],(*p)[4];px;则*(p2)指向哪里&#xff1f; A X[0][1]B X[0][2]C X[1][0]D X[2][0] 官方解释&#xff1a; D int x[6][4], (*p)[4]; p x; 在这里&#xff0c;x 是一个二维数组&#xff0c;它有6行和4列。p 是一个指向具有4个整数的数组的指针。 当你执行…

DevOps-SonarQube整合Jenkins

下载SonarQube Scanner 登录Jenkins服务器&#xff0c;下载SonarQube Scanner wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip安装unzip&#xff0c;需要通过它来解压zip压缩包 yum install -y unzip解压So…

学习笔记——计算机网络(Internet、网络边缘)

一、Internet 网络是由多个计算机和其他网络设备通过通信链路相互连接而形成的互联网&#xff0c;用于实现数据传输和资源共享。它是现代信息社会中不可或缺的基础设施。 1.计算机网络&#xff1a; 通过通信链路连接&#xff1b; 以共享资源为目标&#xff1b; 资源包括&a…

基于Web的论文管理系统设计

目 录 目 录 III 摘 要 V 关键词 V Abstract VI Key Word VI 第一章 绪论 6 1.1系统设计背景 1 1.2系统设计目的与意义 1 1.3国内外现状 2 1.4本文结构 3 第二章 需求分析 3 2.1系统需求分析 4 2.2系统角色设计 4 第三章 系统开发技术 4 3.1 PHP语言简介和特点 5 3.2 Mysql数据…

【项目笔记】java微服务:黑马头条(day02)

文章目录 app端文章查看&#xff0c;静态化freemarker,分布式文件系统minIO1)文章列表加载1.1)需求分析1.2)表结构分析1.3)导入文章数据库1.3.1)导入数据库1.3.2)导入对应的实体类 1.4)实现思路1.5)接口定义1.6)功能实现1.6.1)&#xff1a;导入heima-leadnews-article微服务&am…

学C还是学C++?

计算机专业学生&#xff0c;大一上学期学习了C语言&#xff0c;下学期学校要学C&#xff0c;请问我寒假继续深入学习C还是提前学C,大佬们有什么建议吗&#xff1f;&#xff08;个人感觉C学的不是很明白&#xff0c;链表文件什么的还不是很懂…&#xff09; 这个并没有一个统一的…