JVM 虚拟机 ----> Java 类加载机制

news2024/12/23 19:02:48

文章目录

    • JVM 虚拟机 ----> Java 类加载机制
      • 一、概述
      • 二、类的生命周期
        • 1、类加载过程(Loading)
          • (1)加载
          • (2)验证
          • (3)准备
          • (4)解析
          • (5)初始化
      • 三、类加载的时机
        • 1、主动引用
        • 2、被动引用
      • 四、类与类加载器
        • 1、概述
        • 2、类加载器分类
      • 五、双亲委派模型
        • 1、 双亲委派工作机制
        • 2、 双亲委派的作用
        • 3、实现源码
      • 六、对象创建的过程
        • 1、类加载检查
        • 2、分配内存
        • 3、初始化零值
        • 4、设置对象头
        • 5、构造 init 构造方法

JVM 虚拟机 ----> Java 类加载机制

一、概述

类是在运行期间第一次使用时,被类加载器动态加载至JVM。JVM不会一次性加载所有类。因为如果一次性加载,那么会占用很多的内存

二、类的生命周期

image-20230911173200941

类的生命周期包含以下 七 个阶段:

  • 加载(Loading)
  • 验证(Verification)
  • 准备(Preparation)
  • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸载(Unloading)

1、类加载过程(Loading)

类加载过程包含:加载、验证、准备、解析和初始化 ,一共包括5 个阶段。

可以通过一句谐音来记忆“家宴准备了西式菜” = 家 (加载) 宴 (验证) 准备 (准备) 了西 (解析) 式 (初始化) 菜

(1)加载

加载是类加载的第一个阶段,注意不要混淆。

加载过程完成以下3件事:

    • 通过类的完全限定名称获取定义该类的二进制字节流。
    • 将该字节流表示的静态存储结构转换为Metaspace元空间区的运行时存储结构。
    • 在内存中生成一个代表该类的 Class 对象,作为元空间区中该类各种数据的访问入口。

其中二进制字节流可以从以下方式中获取:

    • ZIP 包读取,成为 JAREARWAR格式的基础。
    • 从网络中获取,最典型的应用是 Applet
    • 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
    • 由其他文件或容器生成,例如由 JSP 文件生成对应的 Class 类。
(2)验证

确保 Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

(3)准备
  • 类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是元空间区的内存。
  • 实例变量不会在这阶段分配内存,它会在对象实例化时,随着对象一起被分配在堆中。应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
  • 初始值一般为 0 值。例如:下面的类变量 value 被初始化为 0 而不是 123
public static int value = 123;
  • 如果类变量是常量,那么它将初始化为表达式所定义的值而不是 0
public static final int value = 123;
(4)解析

将常量池的符号引用替换为直接引用的过程。其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定

(5)初始化

初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 <clinit>()方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。

<clinit>()是由编译器自动收集类中所有类变量的赋值动作静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。所以,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问

例如:以下代码中静态变量i只能赋值,不能访问,因为i定义在静态代码块的后面

public class Test {
    static {
        i = 0;                // 给变量赋值可以正常编译通过
        System.out.print(i);  // 这句编译器会提示“非法向前引用”
    }
    static int i = 1;
}

由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码:

static class Parent {
    public static int A = 1;
    static {
        A = 2;
    }
}

static class Sub extends Parent {
    public static int B = A;
}

public static void main(String[] args) {
     System.out.println(Sub.B);  // 2
}

接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit>() 方法。

虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中,该阻塞非常隐蔽,几乎不会被察觉。

三、类加载的时机

1、主动引用

虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了只有下列六种情况必须对类进行加载:

  • 当遇到 new getstaticputstatic invokestatic 这 4 条字节码指令时,比如 new 一个对象,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。

    • jvm 执行 new指令时会加载类。即:当程序创建一个类的实例对象。
    • jvm 执行 getstatic指令时会加载类。即:程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
    • jvm 执行 putstatic指令时会加载类。即:程序给类的静态变量赋值。
    • jvm 执行 invokestatic指令时会加载类。即:程序调用类的静态方法。
  • 使用 java.lang.reflect包的方法对类进行反射调用时如 Class.forname("..."), 或newInstance() 等等。如果类没初始化,需要触发类的加载。

  • 加载一个类,如果其父类还未加载,则先触发该父类的加载。

  • 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main() 方法的类),虚拟机会先加载这个类。

  • 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了加载,则该接口要在实现类之前被加载。

2、被动引用

除主动引用之外,所有引用类的方式都不会触发加载,称为被动引用

被动引用的常见例子包括:

  • 被动引用的常见例子包括:
System.out.println(SubClass.value);  // value 字段在 SubClass类的父类中定义
  • 通过数组定义来引用类,不会触发此类的加载。该过程会对数组类进行加载,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法
SuperClass[] sca = new SuperClass[10];
  • 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的加载。
System.out.println(ConstClass.HELLOWORLD);

四、类与类加载器

1、概述

两个类相等,需要类本身相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true,也包括使用 instanceof 关键字做对象所属关系判定结果为 true

除此之外,还要求两个类使用同一个类加载器进行加载,因为每一个类加载器都拥有一个独立的类名称空间。

2、类加载器分类

Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:

  • 启动类加载器Bootstrap ClassLoader),使用 C++ 实现,是虚拟机的一部分;
  • 其它类的加载器,使用 Java 实现,独立于虚拟机,继承自抽象类 java.lang.ClassLoader

从 Java 开发人员的角度看,类加载器可以划分得更细致一些:

  • 启动类加载器(Bootstrap ClassLoader),该类加载器负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。例如java.util.*java.io.**java.lang.*类等常用基础库都是由启动类加载器加载。启动类加载器无法被 Java 程序直接引用。
  • 扩展类加载器(Extension ClassLoader),该类加载器是由 ExtClassLoadersun.misc.Launcher$ExtClassLoader)实现,负责将 <JRE_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,例如swing系列、内置的js引擎、xml解析器等以javax开头的扩展类库都是由扩展类加载器加载,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器Application ClassLoader),该类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader)实现。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此也被称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,比如:我们自己编写的自定义类或第三方jar包。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

五、双亲委派模型

应用程序是由三种类加载器互相配合,从而实现类加载,除此之外还可以加入自己定义的类加载器。

类加载器之间的层次关系,称为双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition)来实现,而不是继承关系(Inheritance)。

image-20230911185110986

1、 双亲委派工作机制

一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载

2、 双亲委派的作用

  • 使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一,避免冲突

例如:java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath中的 Object使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中使用的所有的 Object 都是由启动类加载器所加载的 Object

  • 实现热加载,比如Spring Boot DevTools

image-20230911185323025

3、实现源码

以下是抽象类 java.lang.ClassLoader 的代码片段,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException,此时尝试自己去加载。

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

六、对象创建的过程

1、类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

2、分配内存

类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。内存分配的查找方式有 指针碰撞和空闲列表两种。

image-20230911185625360

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"

3、初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值

4、设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式

5、构造 init 构造方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java 程序的视角来看,对象创建才刚开始,<init> 构造方法还没有执行,目前所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 构造方法,把对象按照程序逻辑的意愿进行初始化,这样一个真正可用的对象才算完整创建出来

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

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

相关文章

纯小白安卓刷机1

文章目录 常见的英文意思刷机是什么&#xff1f;为什么要刷机&#xff1f;什么是BL锁&#xff08;BootLoader锁&#xff09;&#xff1f;我的机能够刷机吗&#xff1f;什么是Boot镜像/分区&#xff1f;什么是Recovery镜像/分区&#xff08;缩写为rec&#xff09;&#xff1f;什…

2023-2024年最值得选的Java毕业设计选题大全:2000个热门选题推荐

一、前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; 毕业设计选题非常重要&a…

【数仓建设系列之五】数仓选型架构概览

【数仓建设系列之五】实时数仓选型架构概览 离线数仓&#xff08;Offline Data Warehouse&#xff09;和实时数仓&#xff08;Real-time Data Warehouse&#xff09;是数仓领域两种常见的数据存储和处理架构&#xff0c;它们在数据处理的方式、目标和时间性上有所不同&#xff…

web端三维重建算法-colmap++

vismap vismap 是colmap 版本 &#xff08;1&#xff09; 支持superpoint superglue &#xff08;2&#xff09; 支持netvlad 图像检索 &#xff08;3&#xff09;支持特征点尺度定权 &#xff08;4&#xff09;支持二维码定位 &#xff08;5&#xff09;支持融合gps &#x…

穿破行业增长迷雾,云鲸J4为何能成为“破题之钥”?

文 | 螳螂观察 作者 | 青月 这几年&#xff0c;消费者对于产品的需求一直在变。 像汽车&#xff0c;过去的消费者可能更看重车的安全性、油耗低等&#xff0c;可如今再看消费者对车的需求&#xff0c;车联网服务、自动辅助驾驶等过去被视为“边缘”的能力&#xff0c;正在变…

虚拟机Ubuntu操作系统最基本终端命令(安装包+详细解释+详细演示)

虚拟机及乌班图&#xff08;Ubuntu操作系统&#xff09; 提示&#xff1a;大家需要软件的可以直接在此链接中提取 链接&#xff1a;https://pan.baidu.com/s/1_4VHGTlXjIuVhBINeOuBCA 提取码&#xff1a;nd0c 文章目录 虚拟机及乌班图&#xff08;Ubuntu操作系统&#xff09;终…

医院不良事件管理系统源码 鱼骨图分析 跌倒事件、压疮事件、坠床事件等系统检测,智能上报

医疗不良事件报告系统源码 医疗不良事件报告系统源码旨在建立全面的、统一的医疗不良事件标准分类系统和患者安全术语&#xff0c;使不良事件上报管理更加标准化和科学化。通过借鉴国内外医疗不良事件报告系统的先进经验&#xff0c;根据医疗不良事件的事件类型、处理事件的不…

Linux高性能服务器编程 学习笔记 第二章 IP协议详解

本章从两方面探讨IP协议&#xff1a; 1.IP头部信息。IP头部出现在每个IP数据报中&#xff0c;用于指定IP通信的源端IP地址、目的端IP地址&#xff0c;指导IP分片和重组&#xff0c;指定部分通信行为。 2.IP数据报的路由和转发。IP数据报的路由和转发发生在除目标机器外的所有主…

【SSH】如何删掉远程服务器中的虚拟环境?如何删掉远程服务器中的用户?如何删掉某个文件夹?

文章目录 一、如何删掉远程服务器中的虚拟环境&#xff1f;二、如何删掉远程服务器中的用户&#xff1f;三、如何删掉某个文件夹&#xff1f; 一、如何删掉远程服务器中的虚拟环境&#xff1f; 在Linux系统下删除conda虚拟环境&#xff1a; # 删除虚拟环境 conda remove -n y…

Nginx 中 location 和 proxy_pass 的斜杠问题

location 的斜杠问题比较好理解&#xff0c;不带斜杠的是模糊匹配。例如&#xff1a; location /doc 可以匹配 /doc/index.html&#xff0c;也可以匹配 /docs/index.html。 location /doc/ 强烈建议使用这种 只能匹配 /doc/index.html&#xff0c;不能匹配 /docs/index…

基于SSM的班主任助理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Linux 编译安装中的 configure 命令

用了这么久的 Linux 系统&#xff0c;也许你会发现&#xff0c;在编译安装中&#xff0c;有的服务编译安装需要执行 configure 命令&#xff0c;而有的却不需要&#xff0c;这是为什么呢&#xff1f;也是不是像我一样一头雾水呢&#xff1f;其实这取决于服务的构建系统和配置方…

k8skubectl陈述式及声明式资源管理

k8s:kubectl陈述式及声明式资源管理 一、陈述式资源管理方法1.陈述式资源管理概念2.基本信息查看&#xff08;1&#xff09;查看版本信息&#xff08;2&#xff09;查看资源对象简写&#xff08;3&#xff09;查看集群信息&#xff08;4&#xff09;配置kubectl自动补全&#x…

搭建个人博客系统

效果图&#xff1a; 博客网址&#xff1a; 行秋http://8.137.35.5:8093/#/Home源码链接&#xff1a; QiuShicheng/Qiu-blog (github.com)https://github.com/QiuShicheng/Qiu-blog 视频参考&#xff1a; B站最通俗易懂手把手SpringBootVue项目实战-前后端分离博客项目-Java…

数据结构基础6:二叉树的实现和堆。

二叉树的概念和应用&#xff1a; 一.树的概念和结构&#xff1a;一.树的概念和结构&#xff1a;1.树的概念&#xff1a;2.树的相关概念&#xff1a;3.树的表示&#xff1a; 二.二叉树的概念和结构&#xff1a;1.概念&#xff1a;2.两种特殊的二叉树&#xff1a;1.完全二叉树&am…

一个CVE漏洞预警知识库

CVE 0x01 免责声明 本仓库所涉及的技术、思路和工具仅供安全技术研究&#xff0c;任何人不得将其用于非授权渗透测试&#xff0c;不得将其用于非法用途和盈利&#xff0c;否则后果自行承担。 无exp/poc&#xff0c;部分包含修复方案 0x02 项目导航 2022.12 CVE-2022-3328&a…

管理类联考——数学——汇总篇——知识点突破——应用题——工程

⛲️ 工程问题为常考题型&#xff0c;命题频率相对较高&#xff0c;题型难度属于中等&#xff0c;核心在于效率的有关计算。 1.工作量s、工作效率v、工作时间t三者的关系&#xff1a; 工作量 工作效率 工作时间&#xff08; s v t &#xff09; 工作量工作效率工作时间&am…

第一章 数据库SQL-Server(及安装管理详细)

❄️作者介绍&#xff1a;奇妙的大歪❄️ &#x1f380;个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01;&#x1f380; &#x1f43d;个人简介&#xff1a;云计算网络运维专业人员&#x1f43d; 前言 21 世纪&#xff0c;人类迈入了“信息爆炸时代”&#xff0c…

Java计算机毕业设计基于SpringBoot音乐网项目(附源码讲解)

目录 用户端 第一步&#xff1a;用户注册 第二步&#xff1a;用户登录 第三步&#xff1a;平台首页&#xff08;可查看平台歌单、歌手详细信息操作等等&#xff09; 第四步&#xff1a;查看歌单 第五步&#xff1a;歌单详情操作&#xff08;歌单评价、歌单歌曲下载、歌单…

2021-2023顶会190+篇ViT高分论文总结(通用ViT、高效ViT、训练transformer、卷积transformer等)

今天分享近三年&#xff08;2021-2023&#xff09;各大顶会中的视觉Transformer论文&#xff0c;有190篇&#xff0c;涵盖通用ViT、高效ViT、训练transformer、卷积transformer等细分领域。 全部论文原文及开源代码文末直接领取 General Vision Transformer&#xff08;通用V…