Java 入门指南:JVM(Java虚拟机)——类的生命周期与加载过程

文章目录

    • 类的生命周期
    • 类加载过程
      • 1)载入(Loading)
      • 2)验证(Verification)
        • 文件格式验证
        • 符号引用验证
      • 3)准备(Preparation)
      • 4)解析(Resolution)
        • 符号引用
        • 直接引用
      • 5)初始化(Initialization)

JVM 运行 Java 代码的时候,需要将编译后的字节码文件加载到其内部的运行时数据区域中进行执行。这个过程涉及到了 Java 的类加载机制

![[Pasted image 20240915212223.png]]

类的生命周期

类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期可以简单概括为 7 个阶段:

  1. 加载(Loading):类加载的过程由类加载器(ClassLoader)完成,类加载器负责将字节码文件(.class 文件)加载到 JVM 中。类加载器的主要职责包括:
  • 加载类文件:从指定位置读取字节码文件,并将其转换成字节数组。
  • 创建 Class 对象:将字节数组转换成 Class 对象,并将其存储在方法区中。
  1. 验证(Verification):验证阶段是为了确保类文件的数据符合 JVM 的规范,不会对 JVM 造成危害。验证主要分为四个阶段:
  • 文件格式验证:确保字节流的格式符合 Class 文件格式规范。
  • 元数据验证:确保类的元数据信息(如常量池中的常量)正确无误。
  • 字节码验证:确保字节码指令符合 JVM 规范,不会导致非法操作。
  • 符号引用验证:确保符号引用能正确解析到实际存在的类、接口、方法或字段。
  1. 准备(Preparation):准备阶段主要是为类变量分配内存空间,并设置类变量的初始值。注意,这里的类变量指的是被 static 修饰的变量。实例变量则是在对象实例化时分配内存空间。

  2. 解析(Resolution):解析阶段是将符号引用转换为直接引用的过程。符号引用指的是类名、接口名、方法名等字符串形式的引用,而直接引用则指向目标对象在内存中的地址。

  3. 初始化(Initialization):初始化阶段是执行类构造器 (<clinit>) 方法的过程。在这个阶段,类中的静态变量会被赋予初始值,并执行静态块中的代码。初始化阶段还包括类中非静态方法的调用和类的实例化。

  4. 使用(Using):类初始化完成后即可正常使用。此时可以创建类的实例,调用类的方法等。

  5. 卸载(Unloading):当一个类不再被引用,并且垃圾回收器确定没有对该类的引用后,类加载器会释放该类占用的资源,最终类会被卸载。卸载的过程是由 JVM 自动管理的,通常发生在内存压力较大时。

其中,验证、准备和解析这三个阶段可以统称为连接(Linking)。这 7 个阶段的顺序如下图所示:

![[一个类的完整生命周期.png]]

类加载过程

类的生命周期除去使用和卸载,就是 Java 的类加载过程。这 5 个阶段一般是顺序发生的,但在动态绑定的情况下,解析阶段发生在初始化阶段之后。系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析

![[Pasted image 20240915212736.png]]

1)载入(Loading)

类加载过程的第一步,主要完成下面 3 件事情:

  1. 通过全类名获取定义此类的二进制字节流。
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。

即将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。

虚拟机规范上面这 3 点并不具体,因此是非常灵活的。比如:“通过全类名获取定义此类的二进制字节流” 并没有指明具体从哪里获取( ZIPJAREARWAR、网络、动态代理技术运行时动态生成、其他文件生成比如 JSP…)、怎样获取。

加载这一步主要是通过 类加载器 完成的。类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载由 双亲委派模型 决定

每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 loadClass() 方法)。

加载阶段与连接阶段的部分动作(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。

2)验证(Verification)

JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。

验证阶段这一步在整个类加载过程中耗费的资源还是相对较多的,但很有必要,可以有效防止恶意代码的执行。任何时候,程序安全都是第一位。

验证阶段主要由四个检验阶段组成:

  1. 文件格式验证(Class 文件格式检查)
  2. 元数据验证(字节码语义检查)
  3. 字节码验证(程序语义检查)
  4. 符号引用验证(类的正确性检查)

![[Pasted image 20240915213543.png]]

文件格式验证

文件格式验证这一阶段是基于该类的二进制字节流进行的,主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java 类型信息的要求。除了这一阶段之外,其余三个验证阶段都是基于方法区的存储结构上进行的,不会再直接读取、操作字节流了。

方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

符号引用验证

符号引用验证发生在类加载过程中的解析阶段,具体来说是 JVM 将符号引用转化为直接引用的时候(解析阶段会介绍符号引用和直接引用)。

符号引用验证的主要目的是确保解析阶段能正常执行,如果无法通过符号引用验证,JVM 会抛出异常,比如:

  • java.lang.IllegalAccessError:当类试图访问或修改它没有权限访问的字段,或调用它没有权限访问的方法时,抛出该异常。

  • java.lang.NoSuchFieldError:当类试图访问或修改一个指定的对象字段,而该对象不再包含该字段时,抛出该异常。

  • java.lang.NoSuchMethodError:当类试图访问一个指定的方法,而该方法不存在时,抛出该异常。

3)准备(Preparation)

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

  • 这时候进行内存分配的仅包括类变量( Class Variables ,即静态变量,被 static 关键字修饰的变量,只与类相关,因此被称为类变量),而不包括实例变量。实例变量会在对象实例化时随着对象一块分配在 Java 堆中。

  • 从概念上讲,类变量所使用的内存都应当在 方法区 中进行分配。不过有一点需要注意的是:JDK 7 之前,HotSpot 使用永久代来实现方法区的时候,实现是完全符合这种逻辑概念的。 而在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。

JVM 会在该阶段对类变量(也称为静态变量static 关键字修饰的)分配内存并初始化,对应数据类型的默认初始值,"通常情况"下是数据类型默认的零值(如 00Lnullfalse 等)。但若给变量加上了 final 关键字,在准备阶段变量的值就被赋值为指定的值。

基本数据类型的零值

数据类型零 值
int0
long0L
short(short) 0
char‘\u0000’
byte(byte) 0
booleanfalse
float0.0f
double0.0d
referencenull

也就是说,假如有这样一段代码:

public String a = "Java";
public static String b = "Java";
public static final String c = "JVM";

a 不会被分配内存,而 b 会;但 b 的初始值不是“Java”而是 null

由于 static final 修饰的变量被称作为常量,和类变量不同。常量一旦赋值就不会改变了,所以 c 在准备阶段的值为“JVM”而不是 null

4)解析(Resolution)

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。 解析动作主要针对以下 7 类符号引用进行:

  • 类或接口
  • 字段
  • 类方法
  • 接口方法
  • 方法类型
  • 方法句柄
  • 调用限定符
符号引用

符号引用(Symbol References):以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定是已经加载到虚拟机内存当中的内容。

各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在 Class 文件格式中。符号引用在编译时生成,存储在编译后的字节码文件的常量池中。

例如,在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如 com.Order 类引用了 com.Item 类,编译时 Order 类并不知道 Item 类的实际内存地址,因此只能使用符号 com.Item

直接引用

直接引用(Direct References):可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。通过对符号引用进行解析,找到引用的实际内存地址

直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在。由于直接指向了内存地址或者偏移量,所以通过直接引用访问对象的效率较高。

在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。

5)初始化(Initialization)

初始化阶段是执行初始化方法 <clinit> () 方法的过程,该阶段是类加载过程的最后一步。在准备阶段,类变量已经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码期望赋的值。

例如:

String temp = new String("JVM");

上面这段代码使用了 new 关键字来实例化一个字符串对象,那么这时候,就会调用 String 类的构造方法对 temp 进行实例化。

public String(String original) {this.value = original.value;this.hash = original.hash;
}

对于 <clinit> () 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 <clinit> () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起多个线程阻塞,并且这种阻塞很难被发现。

对于初始化阶段,虚拟机严格规范了有且只有 6 种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):

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

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

  3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。

  4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。

  5. MethodHandleVarHandle 可以看作是轻量级的反射调用机制,而要想使用这 2 个调用,就必须先使用 findStaticVarHandle 来初始化要调用的类。

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【渗透测试】——DVWA靶场搭建

&#x1f4d6; 前言&#xff1a;DVWA&#xff08;Damn Vulnerable Web Application&#xff09;是一个用于安全漏洞测试的 PHP/MySQL 网络应用&#xff0c;旨在为安全专业人士提供一个合法的环境&#xff0c;以测试他们的技能和工具&#xff0c;同时帮助 Web 开发者更好地理解 …

探索自动化的魔法:Python中的pyautogui库

文章目录 探索自动化的魔法&#xff1a;Python中的 pyautogui 库背景&#xff1a;为什么选择pyautogui&#xff1f;pyautogui是什么&#xff1f;如何安装pyautogui&#xff1f;五个简单的库函数使用方法场景应用常见Bug及解决方案总结 探索自动化的魔法&#xff1a;Python中的 …

Cyber Weekly #24

赛博新闻 1、OpenAI发布最强模型o1 本周四&#xff08;9月12日&#xff09;&#xff0c;OpenAI宣布推出OpenAIo1系列模型&#xff0c;标志着AI推理能力的新高度。o1系列包括性能强大的o1以及经济高效的o1-mini&#xff0c;适用于不同复杂度的推理任务。新模型在科学、编码、数…

用nginx-rtmp-win32-master及ffmpeg模拟rtmp视频流

效果 使用nginx-rtmp-win32-master搭建RTMP服务 双击exe就可以了。切记整个目录不能有中文 README.md ,启用后本地的RTM路径: rtmp://192.168.1.186/live/xxx ffmpeg将地本地视频推RMTP F:\rtsp\ffmpeg-7.0.2-essentials_build\bin>ffmpeg -re -i F:\rtsp\123.mp4 -c c…

红黑树前语

目录 概念 性质 红黑树与AVL树的比较 过两天更新红黑树的模拟实现,中秋快乐各位 概念 1. 概念&#xff1a; 是一种搜索二叉树&#xff0c; 但在每个结点上增加一个存储位表示节点的颜色&#xff0c;可以是Red 或 Black。通过对任何一条从根到叶子的路径上各个节点着色方式的…

C++_20_多态

多继承会造成 菱形继承** 使用虚继承来解决 不是给爷爷类加 也不是给子类加 是给父类加 虚基指针和虚基表 多态 概念&#xff1a; 概念&#xff1a; 一个事物的多种形态&#xff0c;简称多态 如&#xff1a; 对象的多态 ​ 张三 ​ 在对象面前 怂 ​ 在朋友面前 谄媚 ​ 在父…

rabbitmq容器化部署

需求 容器化部署rabbitmq服务 部署服务 找到如下官网信息版本 官网版本发布信息 这里看到最新版本是3.13版本&#xff0c;这里在3.13中找一个版本下载容器镜像即可。 找到dockrhub.com中 找到3.13.2版本镜像。 容器服务安装此处省略 现在下载容器镜像需要配置容器代理 ~#…

免费像素画绘制软件 | Pixelorama v1.0.3

Pixelorama 是一款开源像素艺术多工具软件&#xff0c;旨在为用户提供一个强大且易于使用的平台来创作各种像素艺术作品&#xff0c;包括精灵、瓷砖和动画。这款软件以其丰富的工具箱、动画支持、像素完美模式、剪裁遮罩、预制及可导入的调色板等特色功能&#xff0c;满足了像素…

Win电脑使用Ollama与Open Web UI搭建本地大语言模型运行工具

文章目录 前言1. 运行Ollama2. 安装Open WebUI2.1 在Windows系统安装Docker2.2 使用Docker部署Open WebUI 3. 安装内网穿透工具4. 创建固定公网地址 前言 本文主要介绍如何在Windows系统快速部署Ollama开源大语言模型运行工具&#xff0c;并安装Open WebUI结合cpolar内网穿透软…

8. Transforms的使用(三)-- Resize

Transforms的使用(三) 1. 为什么要使用Resize 在模型的训练过程中往往需要图片数据的维度相同,才能适应深度学习模型中的相关神经网络结构,这时候就需要使用Resize保证所有的图片保持相同的尺寸2. 使用Resize调整图片的尺寸 在pytorch2.3的版本上,Resize()支持对Tensor类…

Vue2学习笔记(01计算属性和监视属性)

1、事件修饰符 2、计算属性-computed 要显示的数据不存在&#xff0c;要通过计算得来。在computed对象中定义计算属性。在页面中使用{{方法名}}来显示计算的结果。 3、监视属性-watch 通过vm对象的$watch()或watch配置来监视指定的属性当属性变化时,回调函数自动调用,在函数内…

2024.9.14 Python与图像处理新国大EE5731课程大作业,马尔可夫随机场和二值图割,校正立体图像的深度

1.马尔科夫随机场和二值图割 马尔可夫随机场&#xff08;MRF, Markov Random Field&#xff09;&#xff1a; MRF 是一种用来描述图像像素之间空间关系的概率模型。它假设图像中的像素不仅取决于自身的值&#xff0c;还与周围像素有关。这种模型经常用于图像分割、去噪等任务。…

【C++】关键字、命名空间、输入和输出、缺省参数的深入了解

目录 一、C关键字二、命名空间2.1 为什么存在命名空间&#xff1f;2.2 命名空间定义2.3 命名空间使用 三、C输入&输出四、缺省函数4.1 缺省函数分类 总结 一、C关键字 C一共有63个关键字 其中红色圈出来的32个关键字同时也是C语言的 二、命名空间 2.1 为什么存在命名空间…

MySQL 按照条件(分组)只取一个形成列表 group max

方法一、通过Max形成where条件 SELECTt1.* FROMbiz_customer_hold AS t1 WHEREt1.ch_create_time ( SELECT MAX( ch_create_time ) FROM biz_customer_hold AS t2 WHERE t2.ch_cust_no t1.ch_cust_no ) ORDER BYt1.ch_create_time DESC,t1.ch_hold_time DESC 方法二、通…

LabVIEW编程快速提升的技术

在LabVIEW程序员的成长过程中&#xff0c;很多技术和概念看似简单、常用&#xff0c;但真正掌握并能熟练运用&#xff0c;往往需要踏踏实实的实践与积累。没有什么是能够一蹴而就的&#xff0c;唯有通过不断的专注与深入&#xff0c;才能获得显著的提升。要想在LabVIEW开发上取…

ICPC网络赛 以及ACM训练总结

一、训练反思 关于我自己暑假期间训练的反思&#xff0c;我承认无论是因为什么原因&#xff0c;我自己浪费我整整一个暑假的时间&#xff0c;暑假期间正是我们集训的关键时期&#xff0c;这期间没有任何的事情来打扰我们学习&#xff0c;而我却熬夜&#xff0c;白天训练懈怠&a…

力扣题解2390

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述​&#xff08;中等&#xff09;&#xff1a; 从字符串中移除星号 给你一个包含若干星号 * 的字符串 s 。 在一步操作中&#xff0c;你可以&#xff1a; 选中 s 中的一个星号。 移除星号…

GIS在线监测SF6密度微水传感器免焊接格兰头航插插头

概述 GIS&#xff08;气体绝缘金属封闭开关设备&#xff09;中的SF6&#xff08;六氟化硫&#xff09;气体密度微水传感器航插技术是指在GIS设备中安装SF6气体密度和微水传感器&#xff0c;以实现对SF6气体状态的在线监测。这些传感器能够实时监测SF6气体的密度、微水含量以及其…

Acrobat 9 安装教程

软件介绍 Adobe Acrobat 是由Adobe公司开发的一款PDF&#xff08;Portable Document Format&#xff0c;便携式文档格式&#xff09;编辑软件。借助它&#xff0c;可以以PDF格式制作和保存文档&#xff0c;以便于浏览和打印&#xff0c;同时还可以使用一些高级工具来创建、编辑…

CSS—4

1.定位 1.相对定位 2.绝对定位 3.固定定位 4.粘性定位 5.定位的特殊应用 2.布局-版心 3.布局-常用布局名词 4.布局-重置默认样式