JVM 虚拟机 ----> Java 类加载机制
文章目录
- 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不会一次性加载所有类。因为如果一次性加载,那么会占用很多的内存
二、类的生命周期
类的生命周期包含以下 七 个阶段:
- 加载(Loading)
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
- 使用(Using)
- 卸载(Unloading)
1、类加载过程(Loading)
类加载过程包含:加载、验证、准备、解析和初始化 ,一共包括5 个阶段。
可以通过一句谐音来记忆“家宴准备了西式菜” = 家 (加载) 宴 (验证) 准备 (准备) 了西 (解析) 式 (初始化) 菜
(1)加载
加载是类加载的第一个阶段,注意不要混淆。
加载过程完成以下3
件事:
-
- 通过类的完全限定名称获取定义该类的二进制字节流。
- 将该字节流表示的静态存储结构转换为
Metaspace
元空间区的运行时存储结构。 - 在内存中生成一个代表该类的
Class
对象,作为元空间区中该类各种数据的访问入口。
其中二进制字节流可以从以下方式中获取:
-
- 从
ZIP
包读取,成为JAR
、EAR
、WAR
格式的基础。 - 从网络中获取,最典型的应用是
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
、getstatic
、putstatic
或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),该类加载器是由
ExtClassLoader
(sun.misc.Launcher$ExtClassLoader
)实现,负责将<JRE_HOME>/lib/ext
或者被java.ext.dir
系统变量所指定路径中的所有类库加载到内存中,例如swing
系列、内置的js
引擎、xml
解析器等以javax
开头的扩展类库都是由扩展类加载器加载,开发者可以直接使用扩展类加载器。 - 应用程序类加载器(
Application ClassLoader
),该类加载器是由AppClassLoader
(sun.misc.Launcher$AppClassLoader
)实现。由于这个类加载器是ClassLoader
中的getSystemClassLoader()
方法的返回值,因此也被称为系统类加载器。它负责加载用户类路径(ClassPath
)上所指定的类库,比如:我们自己编写的自定义类或第三方jar
包。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
五、双亲委派模型
应用程序是由三种类加载器互相配合,从而实现类加载,除此之外还可以加入自己定义的类加载器。
类加载器之间的层次关系,称为双亲委派模型(Parents Delegation Model
)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition
)来实现,而不是继承关系(Inheritance
)。
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
3、实现源码
以下是抽象类 java.lang.ClassLoader
的代码片段,其中的 loadClass()
方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException,此时尝试自己去加载。
public abstract class ClassLoader {// The parent class loader for delegationprivate 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 loadedClass<?> 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
堆中划分出来。内存分配的查找方式有 指针碰撞和空闲列表两种。
选择以上两种方式中的哪一种,取决于 Java
堆内存是否规整。而 Java
堆内存是否规整,取决于 GC
收集器的算法是"标记-清除",还是"标记-整理"
3、初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java
代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值
4、设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式
5、构造 init 构造方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java
程序的视角来看,对象创建才刚开始,<init>
构造方法还没有执行,目前所有的字段都还为零。所以一般来说,执行 new
指令之后会接着执行 <init>
构造方法,把对象按照程序逻辑的意愿进行初始化,这样一个真正可用的对象才算完整创建出来
相关文章:

JVM 虚拟机 ----> Java 类加载机制
文章目录 JVM 虚拟机 ----> Java 类加载机制一、概述二、类的生命周期1、类加载过程(Loading)(1)加载(2)验证(3)准备(4)解析(5)初始…...

《protobuf》基础语法2
文章目录 枚举类型ANY 类型oneof 类型map 类型改进通讯录实例 枚举类型 protobuf里有枚举类型,定义如下 enum PhoneType {string home_addr 0;string work_addr 1; }同message一样,可分为 嵌套定义,文件内定义,文件外定义。不…...

利用 SOAR 加快事件响应并加强网络安全
随着攻击面的扩大和攻击变得越来越复杂,与网络攻击者的斗争重担落在了安全运营中心 (SOC) 身上。SOC 可以通过利用安全编排、自动化和响应 (SOAR) 平台来加强组织的安全态势。这一系列兼容的以安全为中心的软件可加快事…...

uni-app:通过ECharts实现数据可视化-如何引入项目
效果 引入文件位置 代码 <template><view id"myChart"></view> </template> <script> import echarts from /static/js/echarts.js // 引入文件 export default {mounted() {// 初始化EChartsconst myChart echarts.init(document…...
string 模拟与用法
string 用法 string string 模拟 #pragma once #include <assert.h> #include <string.h> #include <iostream>namespace sjy {class string{public://迭代器相关typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _st…...

[NLP] LLM---<训练中文LLama2(一)>训练一个中文LLama2的步骤
一 数据集 【Awesome-Chinese-LLM中文数据集】 【awesome-instruction-dataset】【awesome-instruction-datasets】【LLaMA-Efficient-Tuning-数据集】Wiki中文百科(25w词条)wikipedia-cn-20230720-filteredBaiduBaiKe(563w词条) …...

华为云云耀云服务器L实例使用教学 | 利用华为云服务器搭建--> 基于Spring Boot+WebSocket+WebRtc实现的多人自习室
文章目录 1. 购买华为云服务器L2. 在华为云服务器上搭建项目前期准备工作1. 更换登录密码2. 安全组配置 3. 在服务器上运行自己的项目 1. 购买华为云服务器L 在有优惠券的情况下,来到华为云这个网址下面,链接为:https://www.huaweicloud.com…...

Postman应用——接口请求(Get和Post请求)
文章目录 新增请求接口请求Get接口请求Post 这里只讲用的比较多的Get和Post请求方式,也可以遵循restful api接口规范,使用其他请求方式。 GET(SELECT):从服务器取出资源(一项或多项)POST&#…...

k8s pod概念、分类及策略
目录 一.pod相关概念 2.Kubrenetes集群中Pod两种使用方式 3.pause容器的Pod中的所有容器共享的资源 4.kubernetes中的pause容器主要为每个容器提供功能: 6.Pod分为两类: 二.Pod容器的分类 1.基础容器…...
C++系列-左移运算符重载
左移运算符重载 左移运算符的应用左移运算符的重载 左移运算符的应用 左移运算符,左移第一个操作数的位,第二个操作数决定要移动的位置左移运算符还可以用于输出调试,cout << “Hello” << endl; 左移运算符的重载 左移运算符…...
【Vue】vue中v-if的用法
v-if是Vue.js中常用的条件渲染指令,根据表达式的值来动态控制元素的显示或隐藏。具体的使用方法如下: 1.基本语法 <div v-if"condition"><!-- content --> </div>其中,v-if后面跟着一个表达式condition&#x…...

企业架构LNMP学习笔记54
企业架构NoSQL数据库之MongoDB。 学习目标和内容: 1)能够简单描述mongoDB的使用特点: 2)能够安装配置启动MongoDB; 3)能够使用命令行客户端简单操作MongoDB; 4)能够实现基本的数…...

C【函数】
1.常用API 1.strcpy:#include<string.h> char * strcpy ( char * destination, const char * source );int main(){char arr1[] "bit";char arr2[20] "###########";// bit\0########strcpy(arr2, arr1);printf("…...

【简单教程】利用Net2FTP构建免费个人网盘,实现便捷的文件管理
文章目录 1.前言2. Net2FTP网站搭建2.1. Net2FTP下载和安装2.2. Net2FTP网页测试 3. cpolar内网穿透3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 文件传输可以说是互联网最主要的应用之一,特别是智能设备的大面积使用,无论是个人…...

05-Flask-Flask查询路由方式
Flask查询路由方式 前言命令行方式代码实现返回所有路由 前言 本篇来学习下Flask中查询路由的方式 命令行方式 # window 用set linux 用 export set FLASK_APPtest_6_flask运行发方式# 打印所有路由 flask routes代码实现返回所有路由 # -*- coding: utf-8 -*- # Time …...

lua环境搭建数据类型
lua作为一门计算机语言,从语法角度个人感觉还是挺简洁的接下来我们从0开始学习lua语言。 1.首先我们需要下载lua开发工具包 在这里我们使用的工具是luadist 下载链接为:https://luadist.org/repository/下载后的压缩包解压后就能用。 2.接下来就是老生…...

c++11的一些新特性
c11 1. {}初始化2. 范围for循环3. final与override4. 右值引用4.1 左值引用和右值引用4.2 左值引用与右值引用比较 5. lambda表达式6. 声明6.1 auto6.2 decltype6.3 nullptr 7. 可变参数模版 1. {}初始化 在C中,使用花括号初始化的方式被称为列表初始化。列表初始化…...

K8S名称空间和资源配额
Kubernetes 支持多个虚拟集群,底层依赖于同一个物理集群。 这些虚拟集群被称为名称空间。名称空间namespace是k8s集群级别的资源,可以给不同的用户、租户、环境或项目创建对应的名称空间,例如,可以为test、dev、prod环境分别创建各…...

鼠标拖拽拖动盒子时,与盒子内某些点击事件冲突问题解决
目录 问题解决思路解决代码(标注【主要代码】的为重点) 问题 拖动该悬浮球时,鼠标弹起可能会触发悬浮球内事件 解决思路 鼠标拖动盒子时,将 isMove 设为 true 意为正在拖动盒子,此时将 class"btns_move" 遮…...

PMP项目管理证书是什么?有什么用?
什么是PMP证书? PMP全称是Project Management Professional,中文全称叫项目管理专业人士资格认证,是由美国项目管理协会(PMI)发起,严格评估项目管理人员知识技能是否具有高品质的资格认证考试,目的是为了给项目管理人…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...