深入浅出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){
     ​
                     }
                 }
             }
         }
         @Test
         public void test(){
             Runnable runnable = new Runnable() {
                 @Override
                 public 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号线程start
     2号线程start
     1号线程<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 发布!

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

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

相关文章

Qt应用-视频播放器实例

本文讲解Qt视频播放器应用实例。 实现功能 视频的播放暂停、拖动进度控制,声音控制播放列表控制播放区域的暂停控制,全屏控制等。 界面设计 <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"><class>frmVide…

cmd命令开启windows桌面远程控制并设置防火墙允许远程

cmd命令开启桌面远程控制 1、开启之前&#xff1a; 2、使用管理员身份运行cmd 3、执行cmd命令 reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Control\Terminal server" /v fDenyTSConnections /t REG_DWORD /d 0 /f4、如果这台电脑的防火墙打开&#xf…

vue2的ElementUI的form表单报错“Error: [ElementForm]unpected width”修复

1. 问题 ElementUI的form表单&#xff0c;当动态切换显示表单时报错 Error: [ElementForm]unpected width。 翻译过来就是form表单的label宽度width出了问题。 2. 分析 参数说明类型可选值默认值label-width表单域标签的宽度&#xff0c;例如 ‘50px’。作为 Form 直接子元…

第3.3章:StarRocks数据导入--Stream Load

一、概述 Stream Load是StarRocks常见的数据导入方式&#xff0c;用户通过发送HTTP请求将本地文件或数据流导入至StarRocks中&#xff0c;该导入方式不依赖其他组件。 Stream Load作是一种同步导入方式&#xff0c;可以直接通过请求的返回值判断导入是否成功&#xff0c;无法手…

PHP实践:Laravel中事件使用讲解

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…

【wu-lazy-cloud-network】Java自动化内网穿透架构整理

项目介绍 wu-lazy-cloud-network 是一款基于&#xff08;wu-framework-parent&#xff09;孵化出的项目&#xff0c;内部使用Lazy ORM操作数据库&#xff0c;主要功能是网络穿透&#xff0c;对于没有公网IP的服务进行公网IP映射 使用环境JDK17 Spring Boot 3.0.2 版本更新 1…

MySql重要知识梳理

文章目录 一.索引1.索引概述2.索引优缺点3. 索引结构为什么InnoDB存储引擎选择使用Btree索引结构? 4.索引分类思考InnoDB主键索引的Btree高度为多高? 5. 索引语法1.索引语法2.sql性能分析1.SQL执行频率2.慢查询日志3.explain执行计划 3.索引使用规则1.最左前缀法则2.索引失效…

机器学习 深度学习资料 资源machine learning

Kaggle入门&#xff0c;看这一篇就够了 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/25686876 day1-1.什么是机器学习_哔哩哔哩_bilibiliday1-1.什么是机器学习是10天学会机器学习从入门到深度学习的第1集视频&#xff0c;该合集共计62集&#xff0c;视频收藏或关注UP主&a…

时间获取、文件属性获取 2月20日学习笔记

执行两次代码&#xff0c;打印出两次执行过程中新增的文件及删除的文件 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <dirent.h>#def…

Kubernetes安装nginx-controller作为统一网关

nginx-controller是什么呢? 它是一个能调度nginx的一个kubernetes operator,它能监听用户创建,更新,删除NginxConf对象,来调度本地的nginx实现配置的动态更新。如添加新的代理(http,https,tcp,udp),缓存(浏览器缓存,本地缓存),ssl证书(配置本身,ConfigMap,Secret),更新,删除等…

解决pycharm中PIL安装失败

问题&#xff1a;在调用pil时显示pil标红 我在设置中下载每次失败&#xff0c;显示 ERROR: Could not find a version that satisfies the requirement PIL (from versions: none) ERROR: No matching distribution found for PIL我尝试了很久&#xff0c;查看了一些博客 &a…

论文阅读——SqueezeSAM

SqueezeSAM: User-Friendly Mobile Interactive Segmentation 比SAM更小&#xff0c;更快。 框架&#xff1a; 使用的U型结构 使用BatchNorm而不是LayerNorm节省计算&#xff1b; 对于用户点击和框&#xff0c;单独作为通道&#xff0c;前融合和后融合&#xff08;sam只有后融…

相机图像质量研究(30)常见问题总结:图像处理对成像的影响--重影

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

strings.xml补充知识

复数名词 <plurals name"book"><item name"one">book</item><item name"others">books</item> </plurals>int bookCount 4; Resources res getResources(); String bookCount res.getQuantityString(R.…

MQTT 入门介绍

一、简述 MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一种基于发布/订阅&#xff08;publish/subscribe&#xff09;模式的"轻量级"通讯协议&#xff0c;该协议构建于TCP/IP协议上&#xff0c…

XML Map 端口进阶篇——常用关键字和格式化器详解

XML Map 端口是用于在不同XML之间建立关系映射的工具&#xff0c;允许通过拖拽操作实现源XML和目标 XML之间的数据字段映射&#xff0c;除此之外&#xff0c;XML Map 端口还提供了其它丰富多彩的功能&#xff0c;使用户能够更加灵活和高效的处理XML 数据映射任务&#xff0c;让…

golang入门介绍-1

今天开始发布关于go语言入门到实战内容&#xff0c;各位小伙伴准备好。 go介绍 Go语言&#xff08;或 Golang&#xff09;起源于 2007 年&#xff0c;并在 2009 年正式对外发布。是由 Google 公司开发的一种静态强类型、编译型、并发型、并具有垃圾回收功能的编程语言。 Go 是…

SQL防止注入工具类,可能用于SQL注入的字符有哪些

SQL注入是一种攻击技术&#xff0c;攻击者试图通过在输入中注入恶意的SQL代码来干扰应用程序的数据库查询。为了防止SQL注入&#xff0c;你需要了解可能用于注入的一些常见字符和技术。以下是一些常见的SQL注入字符和技术&#xff1a; 单引号 ​&#xff1a; 攻击者可能会尝试…

C#分部类的应用:记录学生信息

目录 一、分部类及其用途 二、实例 再发一个分部类的应用&#xff0c;巩固一下。 一、分部类及其用途 C#中的部分类也被称为分部类。 C#中的部分类是一种将类的定义分成多个部分&#xff0c;每个部分都位于自己的文件中&#xff0c;然后在编译时合并在一起的机制。 部分类…

超平面介绍

超平面公式 (1) 超平面是指n维线性空间中维度为n-1的子空间。它可以把线性空间分割成不相交的两部分。比如二维空间中&#xff0c;一条直线是一维的&#xff0c;它把平面分成了两部分&#xff1b;三维空间中&#xff0c;一个平面是二维的&#xff0c;它把空间分成了两部分。(2…