深入浅出JVM(三)之HotSpot虚拟机类加载机制

HotSpot虚拟机类加载机制

类的生命周期

什么叫做类加载?

类加载的定义: JVM把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终变成可以被JVM直接使用的Java类型(因为可以动态产生,这里的Class文件并不是具体存在磁盘中的文件,而是二进制数据流)

一个类型被加载到内存使用 到 结束卸载出内存,它的生命周期分为7个阶段: 加载->验证->准备->解析->初始化->使用->卸载

其中重要阶段一般的开始顺序: 加载->验证->准备->解析->初始化

验证,准备,解析合起来又称为连接所以也可以是加载->连接->初始化

注意这里的顺序是一般的开始顺序,并不一定是执行完某个阶段结束后才开始执行下一个阶段,也可以是执行到某个阶段的中途就开始执行下一个阶段

还有种特殊情况就是解析可能在初始化之后(因为Java运行时的动态绑定)

基本数据类型不需要加载,引用类型才需要被类加载

类加载阶段

接下来将对这五个阶段进行详细介绍

Loading

加载
  • 加载的作用
  1. 通过这个类的全限定名来查找并加载这个类的二进制字节流

    • JVM通过文件系统加载某个class后缀文件
    • 读取jar包中的类文件
    • 数据库中类的二进制数据
    • 使用类似HTTP等协议通过网络加载
    • 运行时动态生成Class二进制数据流
  2. 将这个类所代表的静态存储结构(静态常量池)转化为方法区运行时数据结构(运行时常量池)

  3. 在堆中创建这个类的Class对象,这个Class对象是对方法区访问数据的"入口"

    • 堆中实例对象中对象头的类型指针指向它这个类方法区的类元数据
  • 对于加载可以由JVM的自带类加载器来完成,也可以通过开发人员自定义的类加载器来完成(实现ClassLoader,重写findClass())

注意

  1. 数组类是直接由JVM在内存中动态构造的,数组中的元素还是要靠类加载器进行加载
  2. 反射正是通过加载创建的Class对象才能在运行期使用反射

Verification

验证
  • 验证的作用

    确保要加载的字节码符合规范,防止危害JVM安全

  • 验证的具体划分

    • 文件格式验证

      目的: 保证字节流能正确解析并存储到方法区之内,格式上符合Java类型信息

      验证字节流是否符合Class文件格式规范(比如Class文件主,次版本号是否在当前虚拟机兼容范围内...)

    • 元数据验证

      目的: 对类的元数据信息进行语义验证

      元数据:简单的来说就是描述这个类与其他类之间关系的信息

      元数据信息验证(举例):

      1. 这个类的父类有没有继承其他的最终类(被final修饰的类,不可让其他类继承)
      2. 若这个类不是抽象类,那这个类有没有实现(抽象父类)接口的所有方法
    • 字节码验证(验证中最复杂的一步)

      目的: 对字节码进行验证,保证校验的类在运行时不会做出对JVM危险的行为

      字节码验证举例:

      1. 类型转换有效: 子类转换为父类(安全,有效) 父类转换为子类(危险)
      2. 进行算术运算,使用的是否是相同类型指令等
    • 符号引用验证

      发生在解析阶段前:符号引用转换为直接引用

      目的: 保证符号引用转为直接引用时,该类不缺少它所依赖的资源(外部类),确保解析可以完成

验证阶段是一个非常重要的阶段,但又不一定要执行(因为许多第三方的类,自己封装的类等都被反复"实验"过了)

在生产阶段可以考虑关闭 -Xverify:none以此来缩短类加载时间

Preparation

准备

准备阶段为类变量(静态变量)分配内存并默认初始化

  • 分配内存

    • 逻辑上应该分配在方法区,但是因为hotSpot在JDK7时将字符串常量,静态变量挪出永久代(放在堆中)
    • 实际上它应该在堆中
  • 默认初始化

    • 类变量一般的默认初始化都是初始化该类型的零值

      类型零值
      byte(byte)0
      short(short)0
      int0
      long0L
      float0.0F
      double0.0
      booleanfalse
      char'\u0000'
      referencenull
    • 特殊的类变量的字段属性中存在ConstantValue属性值,会初始化为ConstantValue所指向在常量池中的值

    • 只有被final修饰的基本类型或字面量且要赋的值在常量池中才会被加上ConstantValue属性

image-20210516122919733.png

Resolution

解析
  • 解析的作用

    将常量池中的常量池中符号引用替换为直接引用(把符号引用代表的地址替换为真实地址)

    • 符号引用

      • 使用一组符号描述引用(为了定位到目标引用)
      • 与虚拟机内存布局无关
      • 还是符号引用时目标引用不一定被加载到内存
    • 直接引用

      • 直接执行目标的指针,相对偏移量或间接定位目标引用的句柄
      • 与虚拟机内存布局相关
      • 解析直接引用时目标引用已经被加载到内存中
  • 并未规定解析的时间

    可以是类加载时就对常量池的符号引用解析为直接引用

    也可以在符号引用要使用的时候再去解析(动态调用时只能是这种情况)

  • 同一个符号引用可能会被解析多次,所以会有缓存(标记该符号引用已经解析过),多次解析动作都要保证每次都是相同的结果(成功或异常)

类和接口的解析

当我们要访问一个未解析过的类时

  1. 把要解析的类的符号引用 交给当前所在类的类加载器 去加载 这个要解析的类
  2. 解析前要进行符号引用验证,如果当前所在类没有权限访问这个要解析的类,抛出异常IllegalAccessError
字段的解析

解析一个从未解析过的字段

  1. 先对此字段所属的类(类, 抽象类, 接口)进行解析

  2. 然后在此字段所属的类中查找该字段简单名称和描述符都匹配的字段,返回它的直接引用

    • 如果此字段所属的类有父类或实现了接口,要自下而上的寻找该字段
    • 找不到抛出NoSuchFieldError异常
  3. 对此字段进行权限验证(如果不具备权限抛出IllegalAccessError异常)

确保JVM获得字段唯一解析结果

如果同名字段出现在父类,接口等中,编译器有时会更加严格,直接拒绝编译Class文件

方法的解析

解析一个从未解析过的方法

  1. 先对此方法所属的类(类, 抽象类, 接口)进行解析

  2. 然后在此方法所属的类中查找该方法简单名称和描述符都匹配的方法,返回它的直接引用

    • 如果此方法所属类是接口直接抛出IncompatibleClassChangeError异常
    • 如果此方法所属的类有父类或实现了接口,要自下而上的寻找该方法(先找父类再找接口)
    • 如果在接口中找到了,说明所属类是抽象类,抛出AbstractMethodError异常(自身找不到,父类中找不到,最后在接口中找到了,说明他是抽象类),找不到抛出NoSuchMethodError异常
  3. 对此方法进行权限验证(如果不具备权限抛出IllegalAccessError异常)

接口方法的解析

解析一个从未解析过的接口方法

  1. 先对此接口方法所属的接口进行解析

  2. 然后在此接口方法所属的接口中查找该接口方法简单名称和描述符都匹配的接口方法,返回它的直接引用

    • 如果此接口方法所属接口是类直接抛出IncompatibleClassChangeError异常
    • 如果此方法所属的接口有父接口,要自下而上的寻找该接口方法
    • 如果多个不同的接口中都存在这个接口方法,会随机返回一个直接引用(编译会更严格,这种情况应该会拒绝编译)
  3. 找不到抛出NoSuchMethodError

Initializtion

初始化

执行类构造器 的过程

  • 什么是 ?

    • javac编译器 在编译期间自动收集类变量赋值的语句和静态代码块合并 自动生成的
    • 如果没有对类变量赋值动作或者静态代码块 可能不会生成 (带有 ConstantValue属性的类变量初始化已经在准备阶段做过了,不会在这里初始化)
  • 类和接口的类构造器

    • 又叫类构造器,与 实例构造器不同,类构造器不用显示父类类构造器调用

      但是父类要在子类之前初始化,也就是完成类构造器

    • 接口

      执行接口的类构造器时,不会去执行它父类接口的类构造器,直到用到父接口中定义的变量被使用时才执行

  • JVM会保证执行 在多线程环境下被正确的加锁和同步(也就是只会有一个线程去执行 其他线程会阻塞等待,直到 完成)

     public class TestJVM {static class  A{static {if (true){System.out.println(Thread.currentThread().getName() + "<clinit> init");while (true){​}}}}@Testpublic void test(){Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "start");A a = new A();System.out.println(Thread.currentThread().getName() + "end");}};​new Thread(runnable,"1号线程").start();new Thread(runnable,"2号线程").start();}​}​/*1号线程start2号线程start1号线程<clinit> init*/

JVM规定6种情况下必须进行初始化(主动引用)

主动引用
  • 遇到new,getstatic,putstatic,invokestatic四条字节码指令

    • new
    • 读/写 某类静态变量(不包括常量)
    • 调用 某类静态方法
  • 使用java.lan.reflect包中方法对类型进行反射

  • 父类未初始化要先初始化父类 (不适用于接口)

  • 虚拟机启动时,先初始化main方法所在的类

  • 某类实现的接口中有默认方法(JDK8新加入的),要先对接口进行初始化

  • JDK7新加入的动态语言支持,部分....

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

(子类访问父类静态变量)

 public class TestMain {static {System.out.println("main方法所在的类初始化");}​public static void main(String[] args) {System.out.println(Sup.i);}}​class Sub{static {System.out.println("子类初始化");}}​class Sup{static {System.out.println("父类初始化");}static int i = 100;}​/*main方法所在的类初始化父类初始化100*/

子类调用父类静态变量是在父类类加载初始化的时候赋值的,所以子类不会类加载

  1. 实例数组
 public class TestArr {static {System.out.println("main方法所在的类初始化");}public static void main(String[] args) {Arr[] arrs = new Arr[1];}}​class Arr{static {System.out.println("arr初始化");}}​/*main方法所在的类初始化*/

例子里包名为:org.fenixsoft.classloading。该例子没有触发类org.fenixsoft.classloading.Arr的初始化阶段,但触发了另外一个名为“[Lorg.fenixsoft.classloading.Arr”的类的初始化阶段,对于用户代码来说,这并不是一个合法的类名称,它是一个由虚拟机自动生成的、直接继承于Object的子类,创建动作由字节码指令anewarray触发. 这个类代表了一个元素类型为org.fenixsoft.classloading.Arr的一维数组,数组中应有的属性和方法(用户可直接使用的只有被修饰为public的length属性和clone()方法)都实现在这个类里。

创建数组时不会对数组中的类型对象(Arr)发生类加载

虚拟机自动生成的一个类,管理Arr的数组,会对这个类进行类加载

  1. 调用静态常量
 public class TestConstant {static {System.out.println("main方法所在的类初始化");}public static void main(String[] args) {System.out.println(Constant.NUM);}}​class Constant{static {System.out.println("Constant初始化");}static final int NUM = 555;}​/*main方法所在的类初始化555*/

我们在连接阶段的准备中说明过,如果静态变量字段表中有ConstantValue(被final修饰)它在准备阶段就已经完成初始默认值了,不用进行初始化

  1. 调用classLoader类的loadClass()方法加载类不导致类初始化

image-20210516130815998.png

卸载

方法区的垃圾回收主要有两部分: 不使用的常量和类

回收方法区性价比比较低,因为不使用的常量和类比较少

不使用的常量

没有任何地方引用常量池中的某常量,则该常量会在垃圾回收时,被收集器回收

不使用的类

成为不使用的类需要满足以下要求:

  1. 没有该类的任何实例对象
  2. 加载该类的类加载器被回收
  3. 该类对应的Class对象没在任何地方被引用

注意: 就算被允许回收也不一定会被回收, 一般只会回收自定义的类加载器加载的类

总结

本篇文章围绕类加载阶段流程的加载-验证-准备-解析-初始化-卸载 详细展开每个阶段的细节

加载阶段主要是类加载器加载字节码流,将静态结构(静态常量池)转换为运行时常量池,生成class对象

验证阶段验证安全确保不会危害到JVM,主要验证文件格式,类的元数据信息、字节码、符号引用等

准备阶段为类变量分配内存并默认初始化零值

解析阶段将常量池的符号引用替换为直接引用

初始化阶段执行类构造器(类变量赋值与类代码块的合并)

  • 参考资料

    • 《深入理解Java虚拟机》
    • 部分图片来源网络

最后(不要白嫖,一键三连求求拉~)

本篇文章笔记以及案例被收入 gitee-StudyJava、 github-StudyJava 感兴趣的同学可以stat下持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 发布!

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

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

相关文章

善于利用GPT确实可以解决许多难题

当我设计一个导出Word文档的功能时&#xff0c;我面临了一个挑战。在技术选型时&#xff0c;我选择了poi-tl这个模板引擎&#xff0c;因为在网上看到了很多关于它的推荐。poi-tl可以根据模板快速导出Word文档。虽然之前没有做过类似的功能&#xff0c;而且项目中也没有用过&…

开年喜报!Walrus成功入选CNCF云原生全景图

近日&#xff0c;数澈软件 Seal &#xff08;以下简称“Seal”&#xff09;旗下开源应用管理平台 Walrus 成功入选云原生计算基金会全景图&#xff08;CNCF Landscape&#xff09;并收录至 “App Definition and Development - Application Definition & Image Build”板块…

Encoder-decoder 与Decoder-only 模型之间的使用区别

承接上文&#xff1a;Transformer Encoder-Decoer 结构回顾 笔者以huggingface T5 transformer 对encoder-decoder 模型进行了简单的回顾。 由于笔者最近使用decoder-only模型时发现&#xff0c;其使用细节和encoder-decoder有着非常大的区别&#xff1b;而huggingface的借口为…

热阻基础理论 --NMOS温度评估

热阻基础理论 器件 温度差 功率 * 热阻 MOS应用实例 1.假如MOS管悬挂或者外壳贴到散热器上&#xff0c;就意味着用CASE到空气的散热热阻会很大&#xff0c; 如下图中的20。 2. 假如MOS管金属面焊接到PCB上&#xff0c;就意味着用CASE到空气的散热热阻会很校&#xff0c; 如…

计算机设计大赛 深度学习人脸表情识别算法 - opencv python 机器视觉

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…

vmware的ubuntu虚拟机因空间满无法启动

正在虚拟机编译android源代码&#xff0c;没注意空间不足&#xff0c;结果回来发现了 Assuming drive cache: write through 的问题&#xff0c;经查是空间不足的原因 按照这个教程&#xff0c;清除出来部分空间&#xff0c;才能进去系统&#xff0c;并且对系统空间做下优化 …

为什么运维要转行

为什么运维要转行 粉丝提问&#xff1a; 在各种APP里经常看到&#xff0c;趁年轻赶紧远离运维&#xff0c;为什么&#xff1f; 互联网老兵是这样回答的&#xff1a; 运维有很多分类&#xff0c;有干实施运维的&#xff0c;有干交付运维的&#xff0c;也有自动化运维&#xf…

07 Redis之持久化(RDB+AOF)

4 Redis持久化 Redis 是一个内存数据库&#xff0c;然而内存中的数据是不持久的&#xff0c;若主机宕机或 Redis 关机重启&#xff0c;则内存中的数据全部丢失。 当然&#xff0c;这是不允许的。Redis 具有持久化功能&#xff0c;其会按照设置以快照或操作日志的形式将数据持…

Stable Diffusion WebUI 界面介绍

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 大家好&#xff0c;我是水滴~~ 本文主要对 Stable Diffusion WebUI 的界面进行简单的介绍&#xff0c;让你对该 WebUI 有个大致的了解&#xff0c;为后面的深入学习打下一个基础。主要内容包…

《VitePress 简易速速上手小册》第1章:VitePress 入门(2024 最新版)

文章目录 1.1 VitePress 简介与架构1.1.1 基础知识点解析1.1.2 重点案例&#xff1a;企业文档站点1.1.3 拓展案例 1&#xff1a;个人博客1.1.4 拓展案例 2&#xff1a;产品展示网站 1.2 安装与初次运行1.2.1 基础知识点解析1.2.2 重点案例&#xff1a;公司内部知识分享平台1.2.…

ts 枚举类型原理及其应用详解

ts 枚举类型介绍 TypeScript的枚举类型是一种特殊的数据类型&#xff0c;它允许开发者为一组相关值定义一个共同的名称&#xff0c;使我们可以更清晰、更一致地使用这些值。 枚举类型在TypeScript中用enum关键字定义&#xff0c;每个枚举值默认都是数字类型&#xff0c;从0开…

前端 webSocket 的使用

webSocket使用 注意要去监听websocket 对象事件&#xff0c;处理我们需要的数据 我是放在了最外层的index 内&#xff0c;监听编辑状态&#xff0c;去触发定义的方法。因为我这个项目是组件化开发&#xff0c;全部只有一个总编辑按钮&#xff0c;我只需监听是否触发了编辑即可…

为什么2023年是AI视频的突破年,以及对2024年的预期#a16z

2023年所暴露的AI生成视频的各种问题&#xff0c;大部分被OpenAI发布的Sora解决了吗&#xff1f;以下为a16z发布的总结&#xff0c;在关键之处&#xff0c;我做了OpenAI Sora的对照备注。 推荐阅读&#xff0c;了解视频生成技术进展。 Why 2023 Was AI Video’s Breakout Year,…

Qt|大小端数据转换(补充)

Qt|大小端数据转换-CSDN博客 之前这篇文章大小端数据转换如果是小数就会有问题。 第一个方法&#xff1a; template <typename T> static QByteArray toData(const T &value, bool isLittle) {QByteArray data;for (int i 0; i < sizeof(T); i) {int bitOffset…

vue3 用xlsx 解决 excel 低版本office无法打开问题

需求背景解决思路解决效果将json导出为excel将table导为excel导出样式 需求背景 原使用 vue3-json-excel &#xff0c;导致在笔记本office环境下&#xff0c;出现兼容性问题 <vue3-json-excel class"export-btn" :fetch"excelGetList" :fields"js…

【Python程序开发系列】利用git实现协同开发做开源贡献(完整过程)

一、问题 假如我在gitee或者github上看到了一个优质的项目&#xff0c;我想对这个项目做一些深入的研究&#xff0c;并对其进行优化&#xff0c;并最终提交PR做出贡献。但是这个项目需要或者最好在虚拟机上或服务器上运行&#xff0c;虚拟机或服务器没有IDE这种代码编辑器&…

2024-02-20(DataX,Spark)

1.Oracle利用DataX工具导出数据到Mysql。Oracle利用DataX工具导出数据到HDFS。 只是根据导入导出的目的地不同&#xff0c;DataX的Json文件书写内容有所不同。万变不离其宗。 书写的Json格式的导入导出规则文件存放再Job目录下的。 2.Spark概念 Apache Spark是用于大规模数…

智能风控体系之逻辑回归

逻辑回归就是这样的一个过程&#xff1a;面对一个回归或者分类问题&#xff0c;建立代价函数&#xff0c;然后通过优化方法迭代求解出最优的模型参数&#xff0c;然后测试验证我们这个求解的模型的好坏。在信贷风控领域最常用的广义线性模型就是逻辑回归。其实逻辑回归线性可分…

说一下JVM类加载机制?

Java中的所有类&#xff0c;都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类&#xff0c;而它的工作就是把class文件从硬盘读取到内存中。 在写程序的时候&#xff0c;我们几乎不需要关心类的加载&#xff0c;因为这些都是隐式装载的&#xff0c;除非我们有特殊…

pc微信逆向最新3.9.8.25版本

朋友让我开发一个关于微信的计数、统计、自动回复功能的机器人&#xff0c;主要是用在win10上面。 先看看结果&#xff01; 之前写过手机端的逆向&#xff0c;PC端逆向很长时间没写了&#xff0c;所以就在网上找了找。基本都是基于3.6&#xff0c;3.7&#xff0c;3.8版本的&a…