JVM的组成

article/2025/10/26 18:23:41

Java Virtual Machine
虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现,java虚拟机有自己完善的硬件架构,如处理器,堆栈,寄存器等,还具有相应的指令系统

一次编译,到处运行

Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行

开篇

程序在执行之前,先要把Java代码编译成字节码(.class文件),JVM首先需要把字节码通过类加载器把文件加载到内存中的运行时数据区中。因为字节码文件是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要执行引擎将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用本地库接口来实现整个程序的功能。
我们通常所说的JVM组成指的是运行时数据区,因为通常需要程序员调试分析的区域就是运行时数据区,或者更具体地说是运行时数据区里面的堆(Heap)模块。

完整构成


基本构成


类加载器-ClassLoader

ClassLoader 负责加载字节码文件即 class 文件
class 文件在文件开头有特定的文件标示

魔数:0X CA FE BA BE()
如果一个文件不是以0xCAFEBABE 开头的,那它肯定不是java class文件

ClassLoader 只负责class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。

执行引擎-Execution Engine

执行引擎,也叫 Interpreter。Class 文件被加载后,会把指令和数据信息放入内存中,Execution Engine 则负责把这些命令解释给操作系统,即将 JVM 指令集翻译为操作系统指令集。

本地接口-Native Interface

本地接口的作用是融合不同的编程语言为Java 所用,初衷是融合C/C++ 程序
它的作用是调用不同语言的接口给 JAVA 用(现在主要是一些硬件交互),它会在 Native Method Stack 中记录对应的本地方法,然后调用该方法时就通过 Execution Engine 加载对应的本地 lib(本地方法库-native libraies)
原本多用于一些专业领域,如JAVA驱动,地图制作引擎等,现在关于这种本地方法接口的调用已经被类似于Socket通信,WebService等方式取代。

运行时数据区-Runtime Data Area

Runtime Data Area 是存放数据的,分为五部分:Stack(虚拟机栈),Heap(堆),Method Area(方法区),PC Register(程序计数器),Native Method Stack(本地方法栈)
几乎所有的关于 Java 内存方面的问题,都是集中在这块


线程私有的内存区域

程序计数器(Program Counter Register)

它是一块较小的内存空间(可能位于cpu的寄存器,有待确认),可以看做是当前字节码指令执行的行号指示器,记录了当前正在执行的虚拟机字节码指令地址,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。
虚拟机的概念模型里,字节码解析器的工作是通过改变这个计数器的值来选取下一条需要执行的字节码指令,比如分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,也就是说在任何时刻,一个处理器(或者说一个内核)都只会执行一条线程中的指令。
每个线程都有各自独立的程序计数器,注意如果正在执行的是 Native方法,则程序计数器为空(Undifined),并且 JVM 规范中并没有对程序计数器定义 OutOfMemoryError 异常。

虚拟机栈(VM Stack)

虚拟机栈也是线程私有的,它描述的是Java方法执行的内存模型每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。
虚拟机栈帧中,局部变量表是比较为人所熟知的,也就是平常所说的“栈”,局部变量表所需的内存空间在编译期间分配完成,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
生命周期与线程相同,不存在垃圾回收,线程结束,内存释放
虚拟机栈有两种异常情况:

  1. StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度,特别是方法的递归调用时(有空程序模拟一下)
  2. OutOfMemoryError:虚拟机栈无法满足线程所申请的空间需求,即使经过动态扩展仍然无法满足时抛出(有空程序模拟一下)

在这里插入图片描述

本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈相似,区别是虚拟机栈为执行 Java 方法服务,而本地方法栈则为 Native 方法服务,有些虚拟机将这两个区域合二为一。

  • 在Execution Engine执行引擎执行时,通过Native Interface 本地接口调用已登记的native library
  • 本地方法栈中抛出异常的情况与虚拟机栈相同,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常

使用场景:与一些底层系统如操作系统或某些硬件交换信息,如打印机

线程共享的内存区域

堆(Heap)

对于java应用程序来讲,堆是jvm所管理的内存中最大的一块。是被所有线程共享的一块区域,并在虚拟机启动时创建。此区域的唯一目的就是存放对象实例,java程序里“几乎”所有的对象实例都会在这里创建并分配内存。

  • 所有的对象实例以及数组都应当在堆上分配

堆也是垃圾收集器所管理的主要区域,因此很多时候也被称作GC堆

从内存回收的角度来看

由于现在收集器基本都采用分代收集算法,因此堆还可以被细分为:新生代和老年代
再继续细分可以分为:Eden空间、From Survivor空间(s0)、To Survivor空间(s1)等

从内存分配的角度来看

线程共享的堆中还可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)
堆可以是物理上不连续的空间,只要逻辑上是连续的即可,-Xmx和-Xms参数可以控制堆的最大和最小值

  • 堆的空间大小不满足时将抛出OutOfMemoryError异常
方法区(Method Area)

用于存储已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等数据。
Java虚拟机规范将方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与java堆区分开来。
误区:方法区不等于永久代

很多人原因把方法区称作“永久代”(Permanent Generation),本质上两者并不等价,只是HotSpot虚拟机垃圾回收器团队把GC分代收集扩展到了方法区,或者说是用来永久代来实现方法区而已,这样能省去专门为方法区编写内存管理的代码,但是在Jdk8也移除了“永久代”,使用Native Memory来实现方法区

在方法区中有一部分区域用来存储编译期产生的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。这里需要说明一点,常量并不是只能在编译期产生,运行期间也会产生新的常量并被发在常量池中,如 String 类的 intern() 方法。
方法区同样会抛出OutOfMemoryError异常

直接内存

注意直接内存不属于虚拟机运行时数据区的一部分,也不是 JVM 规范中定义的内存区域,其主要用于 JDK1.4 引入的基于通道(Channel)和缓冲区(Buffer)的 NIO 类,可以避免在 Native 堆和 Java 堆之间来回复制数据从而提高性能。
该部分内存分配不受 Java 堆内存大小的限制,但是肯定也受限于机器硬件内存的限制,在设置虚拟机参数的时候,不能忽略直接内存,把实际内存设置为-Xmx,使得内存区域的总和大于物理内存的限制,从而导致动态扩展时出现OutOfMemoryError异常。

栈帧构成(Stack Frame)

一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现

每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息,方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程
在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性中了,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。
当前栈帧
一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。
对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是最有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法。
执行引擎运行的所有的字节码指令都只针对当前栈帧进行操作

局部变量表(Local Variable Table

局部变量表(Local Variable Table)是一组变量值存贮空间,用于存放方法参数和方法内定义的局部变量
在Java程序编译为Class文件时候,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量
局部变量表的容量以变量槽为最小单位

变量槽(Variable Slot)

每个变量槽都可以存储32位长度的内存空间,例如boolean、byte、char、short、int、float、reference和returnAddress
对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写
为了节省栈帧空间,局部变量表中的Slot是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超过了某个变量的作用域,那么这个变量对应的Slot就可以交给其他变量使用。
这种机制有时候会影响垃圾回收行为(如大方法占用较多的Slot,执行完该方法的作用域后没有对Slot赋值或者清空设置null值,垃圾回收器便不能及时的回收该内存)
如果执行的是实例方法,那局部变量表中第 0 位索引的 Slot 默认是用于传递方法所属对象实例的引用(在方法中可以通过关键字 this 来访问到这个隐含的参数)
jvm不会给局部变量赋初始值,只给全局变量赋初始值。

操作数栈(Operand Stack)

操作数栈(Operand Stack)也常称为操作栈,是一个后入先出栈。
在Class 文件的Code 属性的 max_stacks 指定了执行过程中最大的栈深度。
Java 虚拟机的解释执行引擎称为”基于栈的执行引擎“,这里的栈就是指操作数栈。

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈 / 入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其它方法的时候是通过操作数栈来进行参数传递的

jvm对操作数栈的优化

在概念模型中,两个栈帧是相互独立的。但是大多数虚拟机的实现都会进行优化,令两个栈帧出现一部分重叠。令下面的部分操作数栈与上面的局部变量表重叠在一块,这样在方法调用的时候可以共用一部分数据,无需进行额外的参数复制传递
操作栈调用其它有返回结果的方法时,会把结果 push 到栈上(通过操作数栈来进行参数传递)

动态链接(Dynamic Linking)

每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)
Class 文件中存放了大量的符号引用,字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接

返回地址(Return Address)

当一个方法开始执行以后,只有两种方法可以退出当前方法

  1. 当执行遇到返回指令,会将返回值传递给上层的方法调用者,这种退出的方式称为正常完成出口(Normal Method Invocation Completion),一般来说,调用者的PC计数器可以作为返回地址
  2. 当执行遇到异常,并且当前方法体内没有得到处理,就会导致方法退出,此时是没有返回值的,称为异常完成出口(Abrupt Method Invocation Completion),返回地址要通过异常处理器表来确定

当方法返回时,可能进行3个操作

  1. 恢复上层方法的局部变量表和操作数栈
  2. 把返回值压入调用者调用者栈帧的操作数栈
  3. 调整 PC 计数器的值以指向方法调用指令后面的一条指令

附加信息(Additional information)

虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧之中,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现。
在实际开发中,一般会把动态连接、方法返回地址与其他附加信息全部归为一类,称为栈帧信息。

帧数据区(Frame Data)

帧数据区的大小依赖于 JVM 的具体实现
这部分的作用主要有三部分:

  • 常量池中数据的解析
  • 方法运行完后处理方法返回,恢复调用方现场
  • 方法运行过程中抛出异常时的异常处理,存储有一个异常表,当出现异常时虚拟机查找相应的异常表看是否有相应的Catch语句,假设没有就抛出异常终止这种方法调

参考

https://java.jverson.com/jvm/jvm-components.html
https://baijiahao.baidu.com/s?id=1720844278862268813&wfr=spider&for=pc
https://blog.csdn.net/wendyyanan/article/details/104261162
https://www.cnblogs.com/jhxxb/p/11001238.html
https://juejin.cn/post/6844903655796113421


http://chatgpt.dhexx.cn/article/k5JO5C42.shtml

相关文章

JVM是什么

什么是JVM? 1、JVM是Java虚拟机,是用来执行Java字节码(二进制的形式)的虚拟机计算机。2、JVM是运行在操作系统之上的,与硬件没有任何关系。 Java的跨平台原理 跨平台:由Java编写的程序可以在不同的操作…

jdk、jre、jvm是什么?

JDK简介 JDK是Java Development Kit的缩写,是Java的开发工具包。 JDK : Java Development ToolKit(Java开发工具包)。JDK是整个JAVA的核心,包括了Java运行环境(JRE),Java工具(javac/java/jdb等&#xff0…

JVM运行原理详解

1.JVM简析: 作为一名Java使用者,掌握JVM的体系结构也是很有必要的。 说起Java,我们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成:Java编程语言、Java类文件格式、Java虚…

JAVA—JVM详解

JAVA—JVM详解 一、JVM 1、什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入Java语言…

什么是JVM?

说明:做java开发的几乎都知道jvm这个名词,但是由于jvm对实际的简单开发的来说关联的还是不多,一般工作个一两年(当然不包括爱学习的及专门做性能优化的什么的),很少有人能很好的去学习及理解什么是jvm&…

JVM介绍(*)

一.什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组…

JVM01_概述、跨平台原理、分类、三大商业虚拟机

文章目录 ①. 什么是JVM?②. Java的跨平台及原理③. JVM的分类④. 三大商业虚拟机 ①. 什么是JVM? ①. JVM 是 java虚拟机,是用来执行java字节码(二进制的形式)的虚拟计算机 ②. jvm是运行在操作系统之上的,与硬件没有任何关系 …

什么是JVM

JVM 即 Java Virtual Machine,中文名为 Java虚拟机。 一般情况下 C/C 程序,编译成二进制文件后,就可以直接执行了;Java 需要使用 javac 编译成 .class 文件,还需要使用 Java 命令去主动执行它。JVM 就是识别 .class …

JVM详解——什么是JVM、JVM优点

JVM详解——什么是JVM、JVM优点 最近在学习Java,经常在书中看到JVM这三个字母,那到底什么是JVM呢? 并且JVM的知识在Java面试中也是非常受面试官青睐的知识点,那对于JVM我们到底要掌握哪些呢? 1、JVM概念 JVM是Jav…

一键禁用Win10自动更新,联想官方出品!!

对于Win10系统的强制更新,真的是是又爱又恨,更新是为了解决电脑系统存在的安全风险以及加入一些新功能,但经常性的开机或者关机都要更新配置,遇到大更新还需要花费很长的等待时间,更加可气的是还会出现更新失败的风险。…

关于禁止win10自动更新的方法:

关于禁止win10自动更新的方法: 1、winr键 输入 services.msc 进去Windows服务管理界面,并且找到Windows update双击进去 在常规中选择禁用,在恢复中选择无操作 2、winr 输入gpedit.msc 回车进入一下界面 在管理模板-windows组件 -window…

禁止windows更新唤醒计算机,windows10睡眠被自动更新唤醒的解决方法

一位用户反馈自己的windows10系统电脑在睡眠状态下总会被自动唤醒,感觉非常烦恼。后来发现每次win10系统开始自动更新操作系统的时候本来睡眠的电脑就会被自动唤醒。那么,我们该如何阻止其唤醒睡眠中的电脑呢?接下来,就随小编一起…

四步教你彻底关闭Win10自动更新

目录 一、禁用Windows Update服务 二、在组策略里关闭Win10自动更新相关服务 三、禁用任务计划里边的Win10自动更新 ​编辑四、在注册表中关闭Win10自动更新 一、禁用Windows Update服务 winR调出运行 >> services.msc >> Windows Update >> …

关于 Win 10 彻底禁用自动更新

Win 10 彻底禁用自动更新 在键盘上按 [ Win R ] 键 ,输入 services.msc 点击确定 。 双击 [ Windows Update ] 。启动类型选择 [ 禁用 ] ,单击 [ 停止 ] 。 点击 [ 恢复 ] ,第一次失败选择 [ 无操作 ] ,然后点击 [ 应用 ] 、[ 确…

Win10禁止更新设备驱动的几种方式

方法一:修改组策略 按下开始菜单并输入 gpedit.msc 搜索打开编辑组策略。 依次展开 计算机配置 → 管理模板 →Windows 组件 →Windows 更新 ; 找到并双击打开 Windows 更新不包括驱动程序 ; 将 未配置 或者 已禁用 改成 已启用 &#xf…

win10禁止chrome自动更新

win10禁止谷歌chrome自动更新 1.进入谷歌安装目录 C:\Users\Administrator\AppData\Local\Google 2.修改update权限 3.修改完成后提示: x

关闭Win10自动更新工具

一、工具下载: 关闭或开启Win10自动更新.EXEhttps://box.lenovo.com/l/t5dQKH 二、支持的操作系统:Windows 10 32位/64位 三、工具介绍:关闭或开启Win10系统的自动更新,涵盖多种版本系统,同时适用于常规方法不能关闭的…

win10怎么更新显卡驱动_如何禁止win10自动更新显卡驱动

在我们使用Win10纯净版的电脑时,经常会遇到电脑玩游戏变的很卡了,本来是优化好的驱动但是显卡驱动被系统自动更新了,那么如何解决这个问题呢?今天小编就给大家介绍下如何禁止win10自动更新显卡驱动。 以下是具体步骤: 1、桌面搜索框中输入并打开“编辑组策略” 在开始菜单…

Win10禁止Nvidia显卡驱动程序自动更新

Win10禁止Nvidia显卡驱动程序自动更新 1、打开设备管理器2、找到设备的类GUID3、WinR运行,调出运行框,输入“gpedit.msc”打开组策略4、输入类GUID 原文链接 1、打开设备管理器 键盘同时按住winx,再按m键,即可调出设备管理器。找…

用批处理文件阻止win10强制更新

win10更新下载的位置win10更新进程隐藏窗口运行和提升执行权限实现反复运行和开机自启 win10这个商业巨头微软的最新之作,以流氓行径著称,尤其是它的自动更新最受诟病。用户无权选择是否下载和安装更新。你选择不更新,他就在后台下载&#x…