还不知道Java类加载机制,你算白学了

news2024/9/30 1:43:07

1 前言

在Java的世界里,每一个类或者接口,在经历编译器后,都会生成一个个.class文件。类加载机制指的是将这些.class文件中的二进制数据读入到内存中,并对数据进行校验,解析和初始化。最终,每一个类都会在方法区保存一份它的元数据,在堆中创建一个与之对应的Class对象。类的生命周期,经历7个阶段,分别是加载、验证、准备、解析、初始化、使用、卸载。

除了使用卸载两个过程,前面的5个阶段 加载、验证、准备、解析、初始化 的执行过程就是类的加载过程。

2 类加载的时机

很多数人在问 “类什么时候加载” 和 “类什么时候初始化” ,从语境上来说,都是在问同一个问题,就是这个.class文件什么时候被读取到虚拟机的内存中,并且达到可用的状态。

但严格意义上来说,加载和初始化,是类生命周期的两个阶段。对于什么时候加载,Java虚拟机规范中并没有约束,各个虚拟机都可以按自身需要来自由实现。但绝大多数情况下,都遵循“什么时候初始化”来进行加载。

什么时候初始化?Java虚拟机规范有明确规定,当符合以下条件时(包括但不限于),虚拟机内存中没有找到对应类型信息,则必须对类进行“初始化”操作:

1.使用new实例化对象时、读取或者设置一个类的静态字段或方法时

2.反射调用时,例如 Class.forName("com.xxx.TestDemo")

3.初始化一个类的子类,会首先初始化子类的父类

4.Java虚拟机启动时标明的启动类

5.JDK8 之后,接口中存在default方法,这个接口的实现类初始化时,接口会其之前进行初始化

初始化阶段开始之前,自然还是要先经历加载、验证、准备 、解析的。

3 类加载的过程

从前言中,我们知道,类的加载过程分 5 个阶段,其中验证、准备、解析 可以归纳为 “连接” 阶段。

图片

需要注意的是,这5个阶段,并不是严格意义上的按顺序完成,在类加载的过程中,这些阶段会互相混合,交叉运行,最终完成类的加载和初始化。 

例如在加载阶段,需要使用验证的能力去校验字节码正确性。在解析阶段,也要使用验证的能力去校验符号引用的正确性。或者加载阶段生成Class对象的时候,需要解析阶段符号引用转直接引用的能力等等......

接下来,我们详细分解一下,这5个阶段,都做了什么事情。

3.1 加载阶段

加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

1.通过一个类的全限定名去找到其对应的.class文件

2.将这个.class文件内的二进制数据读取出来,转化成方法区的运行时数据结构

3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

Java虚拟机并没有规定类的字节流必从.class文件中加载,在加载阶段,程序员可以通过自定义的类加载器,自行定义读取的地方,例如通过网络、数据库等。

3.2 验证阶段

Class文件中的内容是字节码,这些内容可以由任何途径产出,验证阶段的目的是保证文件内容里的字节流符合Java虚拟机规范,且这些内容信息运行后不会危害虚拟机自身的安全。

验证阶段会完成以下校验:

1.文件格式验证:验证字节流是否符合Class文件格式的规范。例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型等等

2.元数据验证对字节码描述的元数据信息进行语义分析,要符合Java语言规范。例如:是否继承了不允许被继承的类(例如final修饰过的)、类中的字段、方法是否和父类产生矛盾

3.字节码验证:对类的方法体进行校验分析,确保这些方法在运行时是合法的、符合逻辑的。

4.符号引用验证:发生在解析阶段,符号引用转为直接引用的时候,例如:确保符号引用的全限定名能找到对应的类、符号引用中的类、字段、方法允许被当前类所访问。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

验证阶段不是必须的,虽然这个阶段非常重要。Java虚拟机允许程序员主动取消这个阶段,用来缩短类加载的时间,可以根据自身需求,使用 -Xverify:none参数来关闭大部分的类验证措施。

3.3 准备阶段

这个阶段,类的静态字段信息(即使用 static 修饰过的变量)会得到内存分配,并且设置为初始值。

对于该阶段有以下几个知识点需要注意:

1、内存分配仅包括 static 修饰过的变量,而不包括实例变量,实例变量得等到对象实例化时分配内存。

2、初始值指的是变量数据类型的默认值,而不是被在Java代码中被显式地赋予的值。但是,当字段信息被 final 修饰成常量(ConstantValue)时,这个初始值就是Java代码中显式地赋予的值。

示例:public static int value = 3

类变量 value 在准备阶段设置的初始值是 0,不是 3。把value赋值为3的 putstatic 指令是在程序编译后,存放于类构造器 <clinit>() 方法中的,所以把 value 赋值为 3 的动作将在初始化阶段才会执行。
当使用 final 修饰后:public static final int value = 3
类变量 value 在准备阶段设置的初始值是 3,不是 0。

3、在JDK8取消永久代后,方法区变成了一个逻辑上的区域,这些类变量的内存实际上是分配在Java堆中的。

3.4 解析阶段

这个阶段,虚拟机会把这个Class文件中,常量池内的符号引用转换为直接引用。主要解析的是 类或接口、字段、类方法、接口方法、方法类型、方法句柄等符号引用。我们可以把解析阶段中,符号引用转换为直接引用的过程,理解为当前加载的这个类,和它所引用的类,正式进行“连接“的过程。对于这段描述,很多人会问出下面几个问题。

什么是符号引用?

Java代码在编译期间,是不知道最终引用的类型,具体指向内存中哪个位置的,这时候会用一个符号引用,来表示具体引用的目标是"谁"。Java虚拟机规范中明确定义了符号引用的形式,符合这个规范的前提下,符号引用可以是任意值,只要能通过这个值能定位到目标。

什么是直接引用?

直接引用就是可以直接或间接指向目标内存位置的指针或句柄。

引用的类型,还未加载初始化怎么办?

当出现这种情况,会触发这个引用对应类型的加载和初始化。

3.5 初始化阶段

这步类加载的最后一个步骤了,初始化的过程,就是执行类构造器 <clinit>()方法的过程。

当初始化完成之后,类中static修饰的变量会赋予程序员实际定义的“值”,同时类中如果存在static代码块,也会执行这个静态代码块里面的代码。

<clinit>() 方法的作用是什么?

还记得么?在准备阶段,已经对类中static修饰的变量赋予了初始值。<clinit>() 方法的作用,就是给这些变量赋予程序员实际定义的“值”。同时类中如果存在static代码块,也会执行这个静态代码块里面的代码。

<clinit>() 方法是什么?

<clinit>() 方法 和 <init> 方法是不同的,它们一个是“类构造器”,一个是实例构造器。Java虚拟机会保证子类<clinit>() 方法在执行前,父类的 <clinit>() 已经执行完毕。而 <init> 方法则需要显性的调用父类的构造器

<clinit>() 方法由编译器自动生成,但不是必须生成的,只有这个类存在static修饰的变量,或者类中存在静态代码块但时候,才会自动生成<clinit>()方法。

加载过程总结

当一个符合Java虚拟机规范的字节流文件,经历 加载、验证、准备、解析、初始化这些阶段相互协作执行完成之后,加载阶段读取到的Class字节流信息,会按虚拟机规定的格式,在方法区保存一份,然后Java 堆中,会创建一个 java.lang.Class 类的对象,这个对象描述了这个类所有信息,也提供了这个类在方法区的访问入口。 

方法区中,使用同一加载器的情况下,每个类只会有一份Class字节流信息;

Java堆中,使用同一加载器的情况下,每个类只会有一份 java.lang.Class 类的对象。

4 类加载器

还记得在前面加载阶段,通过类的全限定名,获取该类字节流数据的这个动作么,类加载器就是用来实现这个动作的。当年为了满足浏览器上 Java Applet 的需求,Java的开发团队设计了类加载器,它独立于Java虚拟机外部,允许程序员按自身需要自行实现类加载器。这是一项非常优秀的创新,它让同一个类可以实现访问隔离、OSGi、程序热部署等等。发展至今,类加载器已经是Java技术体系的一块重要基石。

三层类加载器介绍

启动类加载器(Bootstrap Class Loader):负责加载<JAVA_HOME>\lib 目录,或者被 -Xbootclasspath 参数制定的路径,例如 jre/lib/rt.jar 里所有的class文件。由C++实现,不是ClassLoader子类。

拓展类加载器(Extension Class Loader):负责加载Java平台中扩展功能的一些jar包,包括<JAVA_HOME>\lib\ext 目录中 或 java.ext.dirs 指定目录下的jar包。由Java代码实现。

应用程序类加载器(Application Class Loader):我们自己开发的应用程序,就是由它进行加载的,负责加载ClassPath路径下所有jar包。

双亲委派模型

高端的食材往往只需要最简单的烹饪方式,而保证Java程序稳定运行的双亲委派模式,其实也非常简单:

双亲委派模式其实一句话就可以说清楚:任何一个类加载器在接到一个类的加载请求时,都会先让其父类进行加载,只有父类无法加载(或者没有父类)的情况下,才尝试自己加载。

图片

 ClassLoader 类中有示例,如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    // 首先要保证线程安全
    synchronized (getClassLoadingLock(name)) {
        // 先判断这个类是否被加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 有父类,优先交给父类尝试加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父类加载失败,这里捕获异常,但不需要做任何处理
            }

            if (c == null) {
                // 没有父类,或者父类无法加载,尝试自己加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

双亲委派模型好处

在解答这个问题前,需要先了解一个知识点:不同的类加载器,加载同一个类,结果是虚拟机里会存在两份这个类的信息,所以当判断这两个类是否“相等”时,必定是不相等的。

使用双亲委派模式,可以保证,每一个类只会有一个类加载器。例如Java最基础的Object类,它存放在 rt.jar 之中,这是 Bootstrap 的职责范围,当向上委派到 Bootstrap 时就会被加载。但如果没有使用双亲委派模式,可以任由自定义加载器进行加载的话,Java这些核心类的API就会被随意篡改。

图片

 好了,Java类加载机制掌握这些就足够了,再也不用怕面试官问到这类问题了,可以和他掰扯一下了,你们学会了吗?

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

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

相关文章

Not All Features Matter:Enhancing Few-shot CLIP with Adaptive Prior Refinement

APE是ICCV2023的一篇文章&#xff0c;也是我在这个领域里接触的第一篇文章&#xff0c;这里主要做一下记录。 论文链接&#xff1a;2304.01195.pdf (arxiv.org) 代码链接&#xff1a;yangyangyang127/APE: [ICCV 2023] Code for "Not All Features Matter: Enhancing Fe…

【深度学习注意力机制系列】—— SKNet注意力机制(附pytorch实现)

SKNet&#xff08;Selective Kernel Network&#xff09;是一种用于图像分类和目标检测任务的深度神经网络架构&#xff0c;其核心创新是引入了选择性的多尺度卷积核&#xff08;Selective Kernel&#xff09;以及一种新颖的注意力机制&#xff0c;从而在不增加网络复杂性的情况…

立即开始使用 3D 图像

一、说明 这个故事介绍了使用这种类型的数据来训练机器学习3D模型。特别是&#xff0c;我们讨论了Kaggle中可用的MNIST数据集的3D版本&#xff0c;以及如何使用Keras训练模型识别3D数字。 3D 数据无处不在。由于我们希望构建AI来与我们的物理世界进行交互&#xff0c;因此使用3…

安装LED透明屏需要注意这4点

随着LED显示屏的广泛应用&#xff0c;各种大屏幕随处可见。透明LED显示屏的安装方法多种多样&#xff0c;涵盖了屋顶式、立柱式、挂式、壁挂式和镶嵌式等多种方式。虽然安装LED透明屏不像安装空调等广告载体那样简单&#xff0c;但也并非极其复杂。考虑到LED透明屏的特殊性&…

LM+retrieval

retrievalLM https://acl2023-retrieval-lm.github.io/ 在input层利用retrieval信息 主要是通过通过相似度计算或者重要性计算在datasets中得到与询问x最相关的k个document,讲文档放在询问x前面组成新的LM的输入&#xff0c;获取额外知识以回答问题。 在intermediate layer…

nacos2.2.3 删除永久实例

问题描述 在nacos2.2.3中删除非临时性实例 报错 解决方案 在命令行下执行命令&#xff1a; curl -X DELETE "http://127.0.0.1:8848/nacos/v1/ns/instance?serviceNamenacos-restTemplate-stock&groupNameDEFAULT_GROUP&namespaceIdpublic&ip192.168.1…

全网最牛,接口自动化-Linux系统安装Jenkins+Ant详细步骤

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 在Linux系统上安装…

GPT-4助力数据分析:提升效率与洞察力的未来关键技术 | 京东云技术团队

摘要 随着大数据时代的到来&#xff0c;数据分析已经成为企业和组织的核心竞争力。然而&#xff0c;传统的数据分析方法往往无法满足日益增长的数据分析需求的数量和复杂性。在这种背景下&#xff0c;ChatGPT-4作为一种先进的自然语言处理技术&#xff0c;为数据分析带来了革命…

FastAPI和Flask:构建RESTful API的比较分析

Python 是一种功能强大的编程语言&#xff0c;广泛应用于 Web 开发领域。FastAPI 和 Flask 是 Python Web 开发中最受欢迎的两个框架。本文将对 FastAPI 和 Flask 进行综合对比&#xff0c;探讨它们在语法和表达能力、生态系统和社区支持、性能和扩展性、开发工具和调试支持、安…

一键批量删除文件名中的空格,轻松整理您的文件

随着数字化时代的到来&#xff0c;我们的电脑里积攒了越来越多的文件&#xff0c;但是随之而来的问题是&#xff0c;文件名中的空格可能会导致一些不便和混乱。为了解决这一问题&#xff0c;我们开发了一款便捷实用的工具&#xff0c;可以一键批量删除文件名中的空格&#xff0…

ORM 之 阿里 Fluent-Mybatis主推动态SQL 你学废了吗?

Mybatis作为在东亚开发者市场上占有绝对的使用优势&#xff0c;在中国大陆上讨论Mybatis优化的项目也是挺活跃。 局限于原始Mybatis繁琐的流程&#xff0c;自动代码生成、声明式SQL、动态SQL&#xff0c;以及诸多细节的内容&#xff1a;多租户、多数据源、数据脱敏、SQL审计、…

MyBatis查询数据库之三(#{}vs${},like查询,resultMap,as,多表查询)

目录 查询操作 1.单表查询 1.1 参数占位符#{}和${} 1.2 ${}的优点 1.3 sql注入问题 ​编辑 面试常问&#xff1a;${}与#{}的区别 1.4 like查询 2.多表查询 2.1 返回字典映射&#xff1a;resultMap 2.2 多表查询 &#xff08;1&#xff09;建立 Articalinfo 实体类&a…

所有AI图都在这了

最近一直在玩AI生图&#xff0c;有点拔不出来了&#xff0c;喜欢生图的感觉&#xff0c;日积月累&#xff0c;已经有几千张图片了&#xff0c;想把这些图片都分享出来给大家欣赏&#xff0c;云盘、图片APP&#xff0c;感觉都不好用&#xff0c;最后熬夜码了一个属于自己的图片管…

(树) 剑指 Offer 68 - II. 二叉树的最近公共祖先 ——【Leetcode每日一题】

❓剑指 Offer 68 - II. 二叉树的最近公共祖先 难度&#xff1a;简单 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科 中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是…

2462. 雇佣 K 位工人的总代价

题目描述&#xff1a; 主要思路&#xff1a; 分别维护两个堆&#xff0c;取左右两边最小的那个。 class Solution { public:long long totalCost(vector<int>& costs, int k, int candidates) {priority_queue<int,vector<int>,greater<int>> ql…

清洗无效邮箱地址,让邮件营销更高效

作为一种低成本、高回报的营销手段&#xff0c;电子邮件营销因其传播效率高、营销范围广的特点被广泛应用。虽然现在短视频营销、搜索引擎营销、自媒体营销、内容营销等各种网络营销方式层出不穷。但是在整个网络营销宣传活动中&#xff0c;大多数企业还是会选择把邮件营销作为…

【Java从入门到大牛】File和IO流上篇

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Java从入门到大牛 &#x1f320; 首发时间&#xff1a;2023年8月9日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e…

《合成孔径雷达成像算法与实现》Figure3.5

clc clear all close all%参数设置 TBP 100; %时间带宽积 T 10e-6; %脉冲持续时间%参数计算 B TBP/T; %信号带宽 K B/T; …

如何简化大型网络服务提供商中的DDoS防护事宜

分布式拒绝服务&#xff08;DDoS&#xff09;攻击对服务提供商构成重大威胁&#xff0c;它们有可能破坏关键基础设施并扰乱业务运营。然而对于大型服务提供商而言实施和管理有效的DDoS防护解决方案可能非常复杂且成本高昂。 下面&#xff0c;火伞云将和大家一起探讨如何简化大型…

【云原生】kubernetes控制器deployment的使用

目录 ​编辑 1 Controller 控制器 1.1 什么是 Controller 1.2 常见的 Controller 控制器 1.3 Controller 如何管理 Pod 2 Deployment 2.1 创建 deployment 2.2 查看 deployment 2.3 扩缩 deployment 2.4 回滚 deployment 2.5 删除 deployment 1 Controller 控制器 …