JVM详解(三)—— 类加载过程

本文详细介绍了Java类加载过程,包括加载、验证、准备、解析和初始化五个阶段,并阐述了类加载器的角色及其双亲委派模型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java虚拟机系列文章是参考网上内容总结,或者转载而来,主要用于个人学习记录。
参考文章:
https://blog.youkuaiyun.com/bingduanlbd/article/details/8363734
https://zhuanlan.zhihu.com/p/44670213
https://blog.youkuaiyun.com/goto1997/article/details/91905622

一、类加载过程

类加载指将类的字节码文件(.class)中的二进制数据读入内存,将其放在运行时数据区的方法区内,然后在堆上创建java.lang.Class对象,封装类在方法区内的数据结构。类加载的最终产品是位于堆中的类对象,类对象封装了类在方法区内的数据结构,并且向JAVA程序提供了访问方法区内数据结构的接口。

1、类加载过程

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:

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

共7个阶段,其中验证、准备、解析3个部分统称为连接(Linking)

图片描述

注意:此处的类加载指的是一个.class文件的加载,在Java中.class文件可能是一个类,也可能是一个接口。此处都叫做类加载。整个类加载的过程即:加载→验证→准备→解析→初始化。概括地说即:

图片描述

这里需要注意:从类的加载→验证→准备→初始化,过程是按顺序依次开始的,但是解析比较特殊。为了支持java语言的晚期绑定/动态绑定,有时解析可以在初始化之后才开始。而且,这只是开始顺序,一个阶段通常执行的过程中会激活调用另一个阶段,所以各个阶段只是按照这个顺序开始,而不会等一个阶段完全完成后才进行下一个阶段,各个阶段是交叉混合进行的,所以各阶段并不会严格按照此顺序结束。

1.1、Loading加载

在加载阶段,虚拟机需要完成以下3件事情:

1.1.1、通过一个类的全限定名来获取此类的二进制字节流。

一个类的二进制字节流即.class文件,如何获取一个类的.class文件其实可以通过多种方法实现,譬如:从ZIP包中读取、从网络传输中获取、运行时计算生成(动态代理技术)、从数据库中读取等。

1.1.2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

加载的过程中主要由【类加载器】来完成。类加载器也分为不同种类,具体见下文↓,除了JVM自带的类加载器,用户也可以使用自己定义的类加载器。

1.1.3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

某个类的java.lang.Class对象,即通常所说的一个类的类对象,这个类对象作为程序调用这个类中方法和数据调用的入口。

类对象生成的方式主要有以下5种:

1). 使用new关键字创建对象

2). 使用Class类的newInstance方法(反射机制)

3). 使用Constructor类的newInstance方法(反射机制)

4). 使用Clone方法创建对象

5). 使用(反)序列化机制创建对象。

具体可以看这篇文章:
https://blog.youkuaiyun.com/justloveyou_/article/details/72466416

1.2、Verification验证

验证是连接阶段的第一步,这一阶段的主要目的是为了确保Class文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段是非常重要的,这个阶段是否严谨,直接决定了Java虚拟机是否能够承受恶意代码的攻击,从执行性能的角度上讲,验证阶段的工作量在虚拟机的类加载子系统中又占据了相当大的一部分。
此阶段主要包含如下几个部分的验证:

  1. 文件格式验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证
1.3、Preparation准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量使用的内存都将在方法区中进行分配。(此处需注意的是,准备阶段是为类变量分配内存并设置初始值而不是实例变量,类变量属于class,实例变量属于方法。实例变量将会在对象实例化时随着对象一起被分配在Java堆中)

1.4、Resolution解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析主要包括:

1.类或接口的解析

2.字段解析

3.类方法解析

4.接口方法解析

1.5、Initialization初始化

类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(字节码)。初始化对于类来说,就是执行类构造器<clinit>()方法的过程。

2、类加载器

2.1、类加载器的作用

类加载器,顾名思义就是用来加载类的,但是其作用不仅仅是加载类。因为对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。
说直白点:比较两个类是否“相等”,只有它们是由同一个类加载器加载时,才有意义。对于同一个类,如果由不同类加载器加载,则他们也必然不相等。
(相等包括Class对象的equals方法、isAssignableFrom()方法、isInstance()方法返回的结果,也包括用instanceof关键词判断的情况)

2.2、类加载器之间的关系

应用程序都是由这3种类加载器互相配合进行加载的,如果有必要还可以加入自己定义的类加载器。这些类加载器之间的关系如下图:

在这里插入图片描述

  • 启动类加载器(BootstrapClassLoader):在JVM运行时被创建,负责加载存放在JDK安装目录下的 /jre/lib 的类文件,或者被-Xbootclasspath参数指定的路径中,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。此类加载器是Java虚拟机的一部分,使用native代码(C++)编写,无法被JAVA程序直接引用。
  • 扩展类加载器(Extension ClassLoader):该类加载器负责加载JDK安装目录下的 /jre/lib/ext 的类,或者由java.ext.dirs系统变量指定路径中的所有类库,开发者也可以直接使用扩展类加载器。
  • 应用程序类加载器(AppClassLoader):负责加载用户类路径(Classpath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有定义过自己的类加载器,该类加载器为默认的类加载器。
  • 用户自定义类加载器(User ClassLoader):JVM自带的类加载器是从本地文件系统加载标准的java class文件,而自定义的类加载器可以做到在执行非置信代码之前,自动验证数字签名,动态地创建符合用户特定需要的定制化构建类,从特定的场所(数据库、网络中)取得java class。

图中的层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的根类加载器以外,其余的类加载器都应该有自己的父类加载器(一般不是以继承实现,而是使用组合关系来复用父加载器的代码)。如果一个类收到类加载请求,它首先请求父类加载器去加载这个类只有当父类加载器无法完成加载时(其目录搜索范围内没找到需要的类)子类加载器才会自己去加载。

2.3、双亲委派的优势

使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object(存放于rt.jar中),是所有类的父类,所以任意一个类启动类加载时,都需要先加载Object类。在类加载器来看,所有的加载Object类的请求,都会逐级委托,最后都委托给Bootstrap根类加载器加载,因此Object类在程序的各种类加载器环境中都是同一个类。(否则,系统中出现的Object类都不尽相同则会出现一片混乱)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值