JVM虚拟机篇(一)深入理解JVM:组成部分、运行流程及程序计数器详解

JVM虚拟机篇(一)深入理解JVM:组成部分、运行流程及程序计数器详解

  • JVM虚拟机篇(一)深入理解JVM:组成部分、运行流程及程序计数器详解
    • 一、引言
    • 二、JVM的组成部分
      • 2.1 类加载子系统
      • 2.2 运行时数据区
      • 2.3 执行引擎
      • 2.4 本地接口(Native Interface)
    • 三、JVM的运行流程
      • 3.1 类加载阶段
      • 3.2 程序执行阶段
    • 四、程序计数器详解
      • 4.1 程序计数器的定义与作用
      • 4.2 程序计数器与字节码执行
      • 4.3 程序计数器与异常处理
      • 4.4 程序计数器的特点与限制
    • 五、总结

)

JVM虚拟机篇(一)深入理解JVM:组成部分、运行流程及程序计数器详解

一、引言

Java虚拟机(Java Virtual Machine,JVM)是Java语言能够实现“一次编写,到处运行”这一特性的关键所在。它如同一个幕后的魔法师,将Java代码转换为可以在不同操作系统和硬件平台上执行的指令。深入了解JVM的组成结构、运行流程以及其中各个组件的功能,对于Java开发者来说至关重要。它不仅有助于我们编写出更高质量、性能更优的代码,还能在遇到问题时,更深入地进行分析和调试。接下来,我们就一起深入探究JVM的奥秘。

二、JVM的组成部分

在这里插入图片描述
在这里插入图片描述

2.1 类加载子系统

类加载子系统负责加载字节码文件(.class文件)到JVM中。它主要包含以下几个关键组件:

  • ClassLoader(类加载器):ClassLoader是类加载子系统的核心,它主要有三种类型:
    • 启动类加载器(Bootstrap ClassLoader):它是JVM的内置类加载器,用C++编写(在HotSpot虚拟机中),负责加载Java的核心类库,比如java.lang包下的类。它是最顶层的类加载器,加载的路径是Java安装目录下的lib目录中被虚拟机认可的(按照文件名识别,如rt.jar等)类库。
    • 扩展类加载器(Extension ClassLoader):由Java语言编写,继承自ClassLoader类。它负责加载Java的扩展类库,加载路径是Java安装目录下的lib/ext目录或者由系统变量java.ext.dirs指定的路径中的类库。
    • 应用程序类加载器(Application ClassLoader):也称为系统类加载器,同样由Java语言编写。它负责加载应用程序classpath路径下的类库,我们编写的应用程序代码基本都是由这个类加载器来加载的。
  • Class文件:Class文件是Java源文件经过编译器编译后生成的字节码文件,它包含了类的元数据信息(如类名、父类名、接口名、字段信息、方法信息等)、常量池、字节码指令等内容。这些信息是类加载子系统加载和解析的对象。
  • 运行时数据区:类加载子系统将Class文件加载到JVM后,会在运行时数据区中为类分配相应的内存空间,存储类的相关信息,比如在方法区中存储类的元数据,在堆中分配对象实例(如果该类有对象被创建的话)。

2.2 运行时数据区

运行时数据区是JVM在运行时管理内存的核心区域,它主要包括以下几个部分:

  • 堆(Heap):堆是JVM中最大的一块内存区域,被所有线程共享。它的主要作用是存放对象实例和数组。几乎所有的对象实例都在堆上分配内存。堆可以分为新生代和老年代,新生代又可以进一步细分为伊甸园区(Eden Space)、幸存0区(Survivor 0 Space)和幸存1区(Survivor 1 Space)。对象首先会在伊甸园区分配内存,当伊甸园区空间不足时,会触发Minor GC(新生代垃圾回收),将存活的对象移动到幸存区,经过多次GC后,如果对象仍然存活,会被晋升到老年代。堆的大小可以通过JVM参数(如-Xmx设置最大堆大小,-Xms设置初始堆大小)进行调整。
  • 方法区(Method Area):方法区也是被所有线程共享的区域,用于存储已被加载的类的元数据信息(如类的结构信息、常量池、静态变量、即时编译器编译后的代码缓存等)。在Java 8之前,方法区的实现是永久代(PermGen),从Java 8开始,使用元空间(Meta - Space)来替代永久代。元空间使用本地内存,其大小不再受限于-XX:MaxPermSize参数,而是受限于系统的可用内存。
  • Java栈(Java Stack):Java栈是线程私有的,它描述的是Java方法执行的内存模型。每个方法在执行时都会创建一个栈帧(Stack Frame),栈帧中存储了局部变量表、操作数栈、动态链接、方法返回地址等信息。当方法被调用时,对应的栈帧入栈,方法执行完毕后,栈帧出栈。Java栈的大小可以通过-Xss参数进行设置。
  • 本地方法栈(Native Method Stack):本地方法栈与Java栈类似,也是线程私有的,它主要用于支持Native方法的执行。当Java程序调用Native方法(通常是用C或C++编写的本地代码)时,会在本地方法栈中创建相应的栈帧来管理方法的执行。
  • 程序计数器(Program Counter Register):程序计数器也是线程私有的,它记录了当前线程所执行的字节码指令的地址(行号)。在多线程环境下,每个线程都有自己独立的程序计数器,这样当线程切换时,能够保证线程继续正确地执行。

2.3 执行引擎

执行引擎是JVM的执行核心,它负责执行加载到JVM中的字节码指令。执行引擎主要包含以下几个组件:

  • 解释器(Interpreter):解释器会逐条读取字节码指令,并将其解释为对应平台的机器码并执行。它的优点是启动速度快,因为不需要进行额外的编译工作,但执行效率相对较低,因为每次执行都需要解释。
  • 即时编译器(Just - In - Time Compiler,JIT):即时编译器会在运行时将热点代码(被频繁执行的代码)编译成机器码,这样在后续执行时就可以直接执行编译后的机器码,提高执行效率。HotSpot虚拟机中包含两种即时编译器:C1编译器和C2编译器。C1编译器又称为客户端编译器,它的编译速度较快,适用于对启动速度要求较高的应用场景;C2编译器又称为服务器端编译器,它的编译优化程度更高,适用于对执行效率要求较高的服务器端应用。
  • 垃圾回收器(Garbage Collector,GC):虽然垃圾回收器主要的职责是回收堆中不再使用的对象所占用的内存空间,但它也与执行引擎密切相关。当执行引擎在执行字节码指令时,会产生新的对象并分配内存,同时垃圾回收器会监控堆中对象的存活情况,当发现有不再使用的对象时,会进行垃圾回收操作,以保证堆有足够的空间来分配新的对象。

2.4 本地接口(Native Interface)

本地接口的作用是使Java程序能够调用本地代码(通常是用C或C++编写的代码)。通过本地接口,Java程序可以与操作系统底层进行交互,例如访问本地文件系统、网络接口等。Java提供了JNI(Java Native Interface)来实现Java代码与本地代码的交互。在JNI中,定义了一系列的函数和数据结构,用于在Java虚拟机和本地代码之间传递数据和控制执行流程。

三、JVM的运行流程

3.1 类加载阶段

  1. 加载:首先,类加载器会根据类的全限定名(如com.example.HelloWorld)来查找对应的Class文件。如果是启动类加载器,它会在指定的核心类库路径中查找;如果是扩展类加载器或应用程序类加载器,会在相应的加载路径中查找。找到Class文件后,类加载器会将Class文件中的字节码加载到内存中,并创建一个对应的java.lang.Class对象来表示这个类。
  2. 验证:加载后的字节码需要进行验证,以确保其符合JVM的规范,不会对JVM的安全造成威胁。验证阶段主要包括文件格式验证(检查字节码文件是否符合Class文件的格式规范)、元数据验证(检查类的元数据信息是否符合Java语言规范,如类的继承关系是否正确等)、字节码验证(检查字节码指令是否合法,是否存在安全隐患等)和符号引用验证(检查符号引用是否能正确解析到实际的类、字段、方法等)。
  3. 准备:在准备阶段,JVM会为类的静态变量分配内存,并设置默认初始值。例如,对于public static int num = 10;,在准备阶段,num会被初始化为0,而不是10,因为真正的赋值操作是在初始化阶段进行的。
  4. 解析:解析阶段是将符号引用转换为直接引用的过程。符号引用是在Class文件中使用的一种对类、字段、方法等的符号化表示,而直接引用是可以直接指向目标的指针或句柄等。例如,在字节码中对一个类的引用可能是通过类的全限定名这种符号引用表示的,在解析阶段会将其转换为指向该类在内存中实际位置的直接引用。
  5. 初始化:初始化阶段是类加载过程的最后一步,在这个阶段,JVM会执行类的静态代码块和对静态变量的赋值操作。例如,对于public static int num = 10;,会在这个阶段将num赋值为10,同时静态代码块中的代码也会被执行。

3.2 程序执行阶段

当类加载完成后,JVM就可以开始执行程序了。

  1. 创建线程:JVM会根据程序的入口点(如main方法所在的类)创建主线程。在Java程序中,除了主线程外,还可以创建多个子线程来实现多线程编程。每个线程都有自己独立的Java栈、本地方法栈和程序计数器。
  2. 方法调用与执行:当主线程开始执行时,会从main方法开始调用。在方法调用过程中,会在Java栈中创建相应的栈帧。栈帧中包含了局部变量表(用于存储方法中的局部变量)、操作数栈(用于执行字节码指令时的操作数存储和计算)、动态链接(用于将符号引用转换为直接引用,实现方法调用的动态绑定)和方法返回地址(用于记录方法执行完毕后返回的位置)。执行引擎会按照字节码指令的顺序,从方法的第一条字节码指令开始执行,通过解释器或即时编译器将字节码转换为机器码并执行。在执行过程中,可能会涉及到对其他方法的调用,此时会重复上述过程,在Java栈中创建新的栈帧。
  3. 内存管理与垃圾回收:在程序执行过程中,会不断地创建对象并分配内存,这些对象主要存放在堆中。随着程序的运行,堆中的对象数量会不断增加,当堆空间不足时,垃圾回收器会被触发,对堆中不再使用的对象进行回收,释放内存空间,以保证程序能够继续正常运行。垃圾回收器会根据一定的算法(如标记 - 清除算法、标记 - 整理算法、复制算法等)来判断对象是否存活,并进行相应的回收操作。
  4. 线程结束:当所有线程都执行完毕(例如主线程执行完main方法中的所有代码,子线程也都完成了各自的任务),JVM会进行一些清理工作,然后退出程序。

四、程序计数器详解

4.1 程序计数器的定义与作用

程序计数器是JVM运行时数据区中的一个较小的内存区域,它是线程私有的。其主要作用是记录当前线程所执行的字节码指令的地址(行号)。在Java程序执行过程中,字节码指令是按照顺序依次执行的,程序计数器就像是一个指针,指向当前正在执行的字节码指令的位置。当一条指令执行完毕后,程序计数器会指向下一条要执行的指令。

在多线程环境下,程序计数器的作用尤为重要。由于多个线程是并发执行的,CPU会在不同线程之间进行切换。当一个线程被暂停,另一个线程开始执行时,每个线程都需要能够记住自己上次执行到的位置,以便在下次被调度执行时能够继续正确地执行。程序计数器就为每个线程提供了这样一个记录执行位置的功能,保证了线程切换后能够继续从正确的位置开始执行字节码指令。

4.2 程序计数器与字节码执行

在JVM执行字节码指令的过程中,程序计数器始终与执行过程紧密配合。当JVM加载一个类并开始执行其方法时,首先会将程序计数器设置为方法字节码的起始位置。然后,执行引擎会根据程序计数器所指向的位置,读取相应的字节码指令,并进行解释或编译执行。在执行过程中,每执行完一条字节码指令,程序计数器会自动递增,指向下一条字节码指令的位置。

例如,对于以下简单的Java代码:

public class Test {public static void main(String[] args) {int a = 10;int b = 20;int c = a + b;System.out.println(c);}
}

在编译后的字节码中,会有一系列的指令来实现变量的赋值、加法运算和输出操作。程序计数器会从字节码的第一条指令开始,依次指向每条指令,执行引擎根据程序计数器的指示来执行相应的操作。当执行完变量a的赋值指令后,程序计数器会指向下一条关于变量b赋值的指令,以此类推,直到整个main方法执行完毕。

4.3 程序计数器与异常处理

在Java程序中,异常处理也是与程序计数器密切相关的一个重要方面。当程序在执行过程中遇到异常时,JVM会根据异常的类型和处理机制进行相应的操作。在异常发生时,程序计数器所指向的位置会被记录下来,以便在异常处理完毕后能够正确地恢复程序的执行。

例如,当使用try - catch语句块来捕获异常时,如果在try块中发生了异常,JVM会暂停当前字节码指令的执行,根据异常类型查找对应的catch块。在找到合适的catch块后,程序计数器会被设置为catch块中第一条字节码指令的位置,开始执行异常处理代码。当异常处理完毕后,如果需要继续执行后续代码,程序计数器会根据异常处理的结果和程序的逻辑,被设置为合适的位置,继续执行字节码指令。

4.4 程序计数器的特点与限制

程序计数器是一块非常小的内存区域,它的生命周期与线程相同。当线程创建时,程序计数器也会被创建并初始化;当线程结束时,程序计数器也会随之销毁。由于程序计数器只是记录字节码指令的地址,所以它所占用的内存空间非常小,对JVM的整体性能影响几乎可以忽略不计。

需要注意的是,程序计数器只能记录字节码指令的地址,对于Native方法,由于其不是由字节码指令组成,所以程序计数器无法记录Native方法的执行位置。在执行Native方法时,程序计数器的值通常为空。

五、总结

JVM作为Java语言的核心运行环境,其组成部分和运行流程涵盖了类加载、内存管理、指令执行等多个方面。类加载子系统负责将字节码文件加载到JVM中并进行初始化;运行时数据区管理着程序运行时的内存分配和使用;执行引擎负责执行字节码指令;本地接口则实现了Java程序与本地代码的交互。而程序计数器作为运行时数据区中一个看似微小却不可或缺的部分,在保证线程正确执行字节码指令方面发挥着关键作用。

深入理解JVM的这些知识,不仅有助于我们编写出更高效、更稳定的Java程序,还能在遇到性能问题、内存泄漏等故障时,从JVM的底层原理出发进行分析和解决。随着Java技术的不断发展,JVM也在持续演进和优化,我们需要不断学习和关注JVM的新特性和新变化,以更好地适应和应用Java技术。希望通过本文的介绍,读者能够对JVM有一个更加全面和深入的认识。

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

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

相关文章

elementui的默认样式修改

今天用element ui ,做了个消息提示,发现提示的位置总是在上面,如图: 可是我想让提示的位置到下面来,该怎么办? 最后还是看了官方的api 原来有个自定义样式属性 customClass 设置下就好了 js代码 css代码 效…

游戏引擎学习第204天

回顾并为今天的内容做铺垫 好,现在开始这一集。今天我们将进行一些用户界面编程,觉得这是一个展示如何编写这类代码的好时机。很多人对如何做用户界面代码都很好奇,所以展示一下如何编写是非常有意义的。 我之所以在现在的这个地方做这些工…

我的世界1.20.1forge模组开发进阶教程——TerraBlender

TerraBlender介绍 从模组开发者的视角来看,TerraBlender为Minecraft生物群系类模组的开发提供了全方位的技术支持,显著降低了开发门槛并提升了模组的质量与扩展性: 跨平台兼容性架构支持Forge/Fabric/Quilt/NeoForge四大主流加载器,开发者无需为不同平台单独适配代码客户端…

借助mcpo在open-webui中使用mcp

open-webui前几天发布了0.6版本,我立即进行了升级。新版本中一个重要功能是通过mcpo方式支持了mcp server。本文将介绍mcpo是什么,以及如何在open-webui中使用它。同时,我也会分享几个在接入过程中遇到的问题及解决方案。 首先来介绍mcpo&…

安装gpu版本的dgl

1.先去网址,找到对应版本的dgl,然后下载到本地。 dgl-whl下载地址 我的是python 3.8 ,cuda 11.6. windows 2.在虚拟环境里 输入 pip install E:\dgl-1.0.2cu116-cp38-cp38-win_amd64.whl (因为我下载到E盘里了) 这样GPU版本的d…

PyTorch使用(7)-张量常见运算函数

1. 基本数学运算 1.1 平方根和幂运算 import torchx torch.tensor([4.0, 9.0, 16.0])# 平方根 sqrt_x torch.sqrt(x) # tensor([2., 3., 4.])# 平方 square_x torch.square(x) # tensor([16., 81., 256.])# 任意幂次 pow_x torch.pow(x, 3) # tensor([64., 729., 4096…

Nginx功能及应用全解:从负载均衡到反向代理的全面剖析

Nginx作为一款开源的高性能HTTP服务器和反向代理服务器,凭借其高效的资源利用率和灵活的配置方式,已成为互联网领域中最受欢迎的Web服务器之一。无论是作为HTTP服务器、负载均衡器,还是作为反向代理和缓存服务器,Nginx的多种功能广…

安徽京准:NTP时间同步服务器操作使用说明

安徽京准:NTP时间同步服务器操作使用说明 3.1 连接天线 天线连接到“ANT”口。 3.2 连接电源 将220V电源线连到AC220V座上或将电源适配器(7.5V~12V)接到DC口上。也可以同时接上,提高供电可靠性。 3.3 LAN网口 网线连接到NTP…

Java项目之基于ssm的怀旧唱片售卖系统(源码+文档)

项目简介 怀旧唱片售卖系统实现了以下功能: 用户信息管理: 用户信息新增:添加新用户的信息。 用户信息修改:对现有用户信息进行修改。 商品信息管理: 商品信息添加:增加新的商品(唱片&#x…

基于 Python 的自然语言处理系列(70):检索增强生成(RAG)

1. 什么是 RAG? 在许多大模型(LLM)应用场景中,我们需要使用特定的用户数据,而这些数据并未包含在模型的训练集中。检索增强生成(Retrieval Augmented Generation,RAG)是一种有效的解…

CAD插件实现:所有文字显示到列表、缩放、编辑——CAD-c#二次开发

当图中有大量文字,需要全部显示到一个列表时并缩放到需要的文字时,可采用插件实现,效果如下: 附部分代码如下: private void BtnSelectText_Click(object sender, EventArgs e){var doc Application.DocumentManager.…

Systemd构建自动化备份服务与外部存储管理

实训背景 你是一家数据公司的系统管理员,需设计一套自动化备份系统,满足以下需求: 定期备份:每周日凌晨1点将 /data 目录压缩备份到 /backups。外部存储挂载:插入USB设备时自动挂载到 /mnt/usb,并触发增量…

PostgreSQL中根据另一表的值来更新一个字段

UPDATE table1 SET value t2.new_value FROM table2 t2 WHERE table1.id t2.reference_id; 解释 UPDATE table1:指定要更新的表,不要用别名。 SET value t2.new_value:设置要更新的字段及其新值,这里新值来自 table2。也可更…

#SVA语法滴水穿石# (000)断言基本概念和背景

一、前言 随着数字电路规模越来越大、设计越来越复杂,使得对设计的功能验证越来越重要。首先,我们要明白为什么要对设计进行验证?验证有什么作用?例如,在用FPGA进行设计时,我们并不能确保设计出来的东西没有功能上的漏洞,因此在设计后我们都会对其进行验证仿真。换句话说…

Git 从入门到精通(开源协作特别版)

🧠 Git 从入门到精通(开源协作特别版) ✅ 基础命令 🧰 高级用法 🛠️ 开源实战技巧 🌍 GitHub 社区协作 适合:从0开始 → 熟练开发者 → 参与/维护开源项目 🔰 第1章:…

【SQL】取消sql某一列的唯一值key值的方法

在插入数据到sql时,遇到了这个问题: Duplicate entry ‘XXX’ for key 起因是: 我之前设计表的时候,手动给product_title 这个列加了一个key, key 是这个字段的唯一键约束,就不能重复在这一列存入重复的数…

【小沐学Web3D】three.js 加载三维模型(React Three Fiber)

文章目录 1、简介1.1 Three.js1.2 React Three Fiber 2、测试2.1 初始化环境2.2 app.js修改(显示内置立方体)2.3 app.js修改(显示内置球体)2.4 app.js修改(显示自定义立方体)2.5 app.js修改(显示…

本地部署 Firecrawl 爬虫让 AI 知识库更丰满

https://www.firecrawl.dev/ firecrawl-logo-with-fire.png 什么是Firecrawl Firecrawl 是一款 可以将网站转换为 便于AI处理的Markdown 格式的爬虫工具 ,主要 提供 API 服务 ,无需站点地图,只需要接收一个 URL 地址就可以爬取网站及网站下可…

纯个人整理,蓝桥杯使用的算法模板day2(0-1背包问题),手打个人理解注释,超全面,且均已验证成功(附带详细手写“模拟流程图”,全网首个

算法索引 01背包优化前空间优化版(使用一维数组)优化后的模拟流程图为何优化后,j不能使用正序遍历模拟流程图 代码对应实现案例 01背包 优化前 /*** 0-1背包问题解法(与下方代码表格示例对应,已模拟验证)*…

APang网联科技项目报告【服务器篇】

APang网联科技:连接未来,智能领航 公司简介 APang网联科技成立于 [2005年],总部位于 [广东深圳],是一家集网络技术研发、系统集成、项目实施与运维服务为一体的高新技术企业。我们致力于为客户提供全方位、定制化的网络部署解决…