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类加载机制

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flex布局实现一部分元素左对齐,一部分右对齐

单个Flex容器内有三个靠右对齐的按钮&#xff0c;如图&#xff1a; display: flex; justify-content: flex-end; 现在需让红色按钮靠左&#xff0c;而另外两个蓝色按钮保持靠右&#xff1a; 方法一&#xff1a; 为红色按钮单独加上&#xff1a;flex: 1; 原理是&#xff1a;利用…

【Docker】docker-compose安装

中文网上复制粘贴的很多&#xff0c;尤以docker-compose为甚。搜索引擎上能搜到的&#xff0c;github的那个网址&#xff0c;curl显示要十几个小时&#xff08;蛮奇怪&#xff0c;win主机直接访问下载就很快&#xff0c;虚拟机Linux去curl就很慢&#xff09;。daocloud的那个&a…

php对接谷歌admob广告收益reporting api分享

今天收到需求,需要对接reporting api接口&#xff0c;拉取广告收益回来。网上找到文档开始对接&#xff0c;对接完成了&#xff0c;今天分享给大家一些心得 文档地址:https://developers.google.com/admob/api/v1/reporting?hlzh-cn#php-client-library 因为接口使用的google…

闪回技术

目录 闪回技术 恢复mybonus表 彻底删除mybonus表 清空回收站 Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 闪回技术 Flash Back 给予用户最为直接的支持之一就是给了用户后悔的机会 但是现在如果用户想去操作这个…

全网最完整的iperf测试工具使用说明

文章目录 前言iPerf 2.0、iPerf 3.0 和 iPerf 3.1 之间的变化iPerf 3 用户文档iPerf 2.0.6、iPerf 2.0.7 和 iPerf 2.0.8 之间的更改iPerf 2 用户文档调整 TCP 连接调整 UDP 连接组 播IPv6 模式使用代表性流测量带宽将服务器作为守护程序运行 前言 iPerf - TCP、UDP 和 SCTP 的…

VUE实现Provide的计算属性

通过此篇可以学到&#xff1a; 如何使用Providerinject进行“跨代”传值如何实现一个计算属性的Provider如何解决告警“injection "xxxxx" not found. ” 一、描述 目前需要创建一个计算属性传入Provide&#xff0c;并且能够被其他组件Inject 二、实现 父组件 .…

每日一题——LeetCode1678.设计Goal解析器

方法一 splice 将字符串转为数组&#xff0c;对数组进行遍历&#xff0c;碰到G保持不变&#xff0c;继续循环&#xff0c;碰到 ( 看他后一位&#xff0c;是 ) 则删除两个元素&#xff0c;添加一个 o &#xff0c;不是则删除四个元素&#xff0c;添加元素 al &#xff0c;最后将…

Python—实例练习

1.编写程序x1&#xff0c;请先输入a和b两个整数&#xff0c;然后编写程序并输出 # (1)计算并显示a的绝对值&#xff1b; print("请输入a&#xff1a;") aint(input())if a>0:print(a) else:print(-a) # (2)两数中的最大值&#xff1b; a int(input()) b int(…

ProcessOn:让你的思维导图与流程图绘制更加高效

ProcessOn&#xff1a;让你的思维导图与流程图绘制更加高效 在当今这个信息爆炸的时代&#xff0c;有效地组织和呈现我们的想法变得尤为重要。无论是学生、教师、项目经理还是设计师&#xff0c;一个好的思维导图或流程图工具都能让我们的工作和学习更加高效。今天&#xff0c…

【项目笔记】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…

管理application的secret,在哪个level呢

从安全设计来看&#xff0c;访问控制是非常重要的。除非是完全公开的网页&#xff0c;可以没有任何限制的访问 在实施访问控制的应用application中呢&#xff0c;你的秘钥管理控制在哪个level呢 level -2 没有访问控制&#xff0c;注意这是-2 负二&#xff0c;不是level 2 l…

H5炫酷DJ背景引导页源码

源码名称&#xff1a;炫酷DJ背景引导页源码 源码介绍&#xff1a;一款带有动态视频背景的引导页源码&#xff0c;带有四个按钮&#xff0c;右下角也有三个按钮。 需求环境&#xff1a;H5 下载地址&#xff1a; https://www.changyouzuhao.cn/11665.html

今天做了两个工具

URL可用性检测 2.影视 第一个工具为第二个工具服务&#xff0c;一定程度上检测了搜集到视频解析接口是否可用。

2023年终总结——跌跌撞撞不断修正

目录 一、回顾1.一月&#xff0c;鼓足信心的开始2.二月&#xff0c;焦躁不安3.三月&#xff0c;路还是要一步一步的走4.四月&#xff0c;平平淡淡的前行5.五月&#xff0c;轰轰烈烈的前行6.六月&#xff0c;看事情更底层透彻了7.七月&#xff0c;设计模式升华月8.八月&#xff…

微信小程序(五十八)分步表单多页面传值

注释很详细&#xff0c;直接上代码 新增内容&#xff1a; 1.分步表单传值 2.伪数据生成 源码&#xff1a; app.json {"pages": ["pages/index/index","pages/building/building","pages/room/room","pages/logs/logs"],&qu…

初步了解变量

为什么需要变量 初识变量 变量的概念&#xff1a; 内存中的一个存储区域&#xff0c;该区域的数据可以在同一类型范围内不断变化 变量的构成包含三个要素&#xff1a;数据类型、变量名、存储的值 Java中变量声明的格式&#xff1a;数据类型 变量名 变量值 变量的作用&…

学习vue3第五节(reactive 及其相关)

1、定义 reactive() 创建一个响应式代理对象&#xff0c;不同于ref()可以创建任意类型的数据&#xff0c;而reactive()只能是对象&#xff0c;会响应式的深层次解包任何属性&#xff0c;将其标注为响应式 响应式是基于ES6的proxy实现的代理对象&#xff0c;该proxy对象与原对象…

九数分三组

枚举三位数时&#xff0c;不用写三个循环&#xff0c;写出最小和最大数循环就行。在这题里要求三个数中不能有重复的数字&#xff0c;先转换为字符串&#xff0c;再转换为字符数组进行排序&#xff0c;最后比较字符串就可以得出结果。这题把结果和原因调换了一下

Mysql/Redis缓存一致性

如何保证MySQL和Redis的缓存一致。从理论到实战。总结6种来感受一下。 理论知识 不好的方案 1.先写MySQL&#xff0c;再写Redis 图解说明: 这是一幅时序图&#xff0c;描述请求的先后调用顺序&#xff1b; 黄色的线是请求A&#xff0c;黑色的线是请求B&#xff1b; 黄色的…

[Linux][CentOs][Mysql]基于Linux-CentOs7.9系统安装并配置开机自启Mysql-8.0.28数据库

目录 一、准备工作&#xff1a;获取安装包和相应工具 &#xff08;一&#xff09;所需安装包 &#xff08;二&#xff09;安装包下载链接 &#xff08;三&#xff09;在服务器上创建文件夹并上传安装包 二、安装MySql &#xff08;一&#xff09;删除系统自带的mariadb …