深入理解Java虚拟机:(二)Java虚拟机的类加载机制

三、类加载的过程


接下来,我们来详细介绍下 Java 虚拟机中类加载的全过程,也就是加载、验证、准备、解析和初始化5个阶段所执行的具体动作。

1、加载

加载,是指通过类的全限定名来获取此类的二进制文件流,将这个字节流所代表的静态存储结构转化为方法区的运行时的数据结构,然后在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

以我要盖房子为例,正好我的好兄弟树根是一级建筑师,我叫他帮我设计一套“四房两厅两卫” 的房型。这里的房型相当于类,而我的好兄弟树根(建筑师)就相当于类加载。

树根是学建筑的,他们班有好多建筑师,但他们有共同的老师——学校的建筑教授,叫启动类加载器(bootstrap class loader)。启动类加载器是由 C++ 实现的,没有对应的 Java 对象,因此在 Java 中只能用 null 来指代。换句话说,教授不喜欢像 树根 这样的小角色来打扰他,所以谁也没有教授的联系方式。

除了启动类加载器之外,其他的类加载器都是 java.lang.ClassLoader 的子类,因此有对应的 Java 对象。这些类加载器需要先由另一个类加载器,比如说启动类加载器,加载至 Java 虚拟机中,方能执行类加载。

学校的建筑师有一个潜规则,就是接到了单子自己不能着手干,得先给教授过过目,教授不接手的情况下,才能自己来。在 Java 虚拟机中,这个潜规则有个特别的名字,叫双亲委派模型。每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。

在 Java 9 之前,启动类加载器负责加载最为基础、最为重要的类,比如存放在 JRE 的 lib 目录下 jar 包中的类(以及由虚拟机参数 -Xbootclasspath 指定的类)。除了启动类加载器之外,另外两个重要的类加载器是扩展类加载器(extension class loader)和应用类加载器(application class loader),均由 Java 核心类库提供。

扩展类加载器的父类加载器是启动类加载器。它负责加载相对次要、但又通用的类,比如存放在 JRE 的 lib/ext 目录下 jar 包中的类(以及由系统变量 java.ext.dirs 指定的类)。

应用类加载器的父类加载器则是扩展类加载器。它负责加载应用程序路径下的类。(这里的应用程序路径,便是指虚拟机参数 -cp/-classpath、系统变量 java.class.path 或环境变量 CLASSPATH 所指定的路径。)默认情况下,应用程序中包含的类便是由应用类加载器加载的。

除了由 Java 核心类库提供的类加载器外,我们还可以加入自定义的类加载器,来实现特殊的加载方式。举例来说,我们可以对 class 文件进行加密,加载时再利用自定义的类加载器对其解密。

在 Java 虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的。即便是同一串字节流,经由不同的类加载器加载,也会得到两个不同的类。在大型应用中,我们往往借助这一特性,来运行同一个类的不同版本。

2、连接

连接,是指将创建成的类合并至 Java 虚拟机中,使之能够执行的过程。它可分为验证、准备以及解析三个阶段。

(1)、验证

验证阶段是非常重要的,这个阶段是否严谨,直接决定可 Java 虚拟机是否能承受恶意代码的攻击。从整体上看,大致会完成下面4个阶段的校验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

  • 文件格式验证

是否以魔数 0xCAFEBABE 开头。

常量池的常量是否有不被支持的常量类型(检查常量 flag 标志)。

指向常量的各种索引值是否有指向不存在的常量或不符合类型的常量。

Class 文件中各个部分及文件本身是否有被删除的或附加的其他信息。

  • 元数据验证

这个类是否有父类。

这个类的父类是否继承了不允许被继承的类(被 final 修饰的类)。

如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。

类中的字段、方法是否与父类产生矛盾。

  • 字节码验证

保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作。

保证跳转指令不会跳转到方法体以外的字节码指令上。

保证方法体中的类型转换是有效的。

  • 符号引用验证

符号引用中通过字符串描述的全限定名是否能找到对应的类。

符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。

(2)、准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这里有两个点容易混淆强调一下,首先,这时候进行内存分配的仅包括类变量(被 static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。其次,这里所说的初始值通常情况下是数据类型的零值,假设一个类变量的定义为:

public static int value = 123;

那变量 value 在准备阶段过后的初始值为 0 而不是 123,因为这时候尚未执行任何 Java 方法,而把 value 赋值为 123 的 putstatic 指令是程序被编译后,存放于类构造器 <clinit>() 方法之中,所以把 value 赋值为 123 的动作将在初始化阶段才会执行。

下表是 Java 所有基本数据类型所对应的零值。

在这里插入图片描述

上面提到,在通常情况下初始值是零值,那相对的会有一些特殊情况:如果类变量 value 的定义变为:

public static final int value = 123;

编译时 Javac 将会为 value 生成 ConstantValue 属性,在准备阶段虚拟机就会根据 ConstantValue 的设置将 value 赋值为 123。

(3)、解析

解析阶段是虚拟机将常量池内的符号引用替换成直接引用的过程。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

3、初始化

类初始化阶段是类加载过程的最后一步,初始化阶段是执行类构造器 <clinit>()方法的过程。Java 虚拟机会通过加锁来确保类的 <clinit>() 方法仅被执行一次。

只有当初始化完成之后,类才正式成为可执行的状态。这放在我们盖房子的例子中就是,只有当房子装修过后,我才能住进去。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!

下面给大家分享下我的面试大全资料

  • 第一份是我的后端JAVA面试大全

image.png

后端JAVA面试大全

  • 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 第三份是Spring全家桶资料

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
+JAVA核心知识整理

  • 第三份是Spring全家桶资料

[外链图片转存中…(img-bHKKhdEk-1713748836221)]

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值