JVM内存模型(运行时数据区)

article/2025/7/19 15:56:35

目录

 编者想说

1、内存模型图

2、栈

3、程序计数器

3、堆

4、方法区(元空间)

5、本地方法栈(Native Method Stack)


 编者想说

        通过上一篇文章的对JVM的体系结构以及它的演化,我们对JVM有了一个比较清晰的认识,接下来我们将会深入其中的运行时数据区(也就是我们常说的JVM内存模型)进行更加深入的一个分析。

1、内存模型图

2、栈

首先,我们以下面这个程序为例来介绍一下栈这个内存区:

import java.util.Scanner;public class Test {public static void main(String[] args) {int a,b;Scanner sc = new Scanner(System.in);a = sc.nextInt();b = sc.nextInt();System.out.println("a+b="+add(a,b));}private static int add(int a, int b) {return a+b;}
}

在这个栈里面存储的叫栈帧(Stack Frame)​​,每个方法调用对应一个栈帧,包含:

局部变量表​:方法内的局部变量(基本类型、对象引用),比如上面程序中的a,b以及sc。

操作数栈​:方法执行时的临时操作数(如算术运算的中间结果a+b)。

动态链接​:指向方法区中该方法的类信息。

方法返回地址​:方法执行完毕后的返回位置。

下面是我对这个程序执行之后在栈内存中的一些动作做的一个图示,方便大家理解(如果有错误,请各位大佬严肃批评指正)。

此处,我要对上图做一些解释:

1、在创建a,b时,这两个临时变量是没有被赋值的,系统自动初始化值为0

2、sc是一个引用数据类型,因为sc是通过new关键字创建的,会指向运行时数据区中的堆内存,而此时栈内存中的sc其实指向堆内存中Scanner实例的地址(因为对象本身在堆中)。

3、a = sc.nextInt()和 b = sc.nextInt()这两个方法是通过sc读取我们输入的值并给a,b做一个赋值,调用nextInt()时,会临时压入nextInt方法的栈帧(执行完毕弹出),接着将值传给a,b,覆盖初始值0。

4、add(a,b)调用add()方法并传入a,b的值,在main栈帧的操作数栈中压入a,b的值然后创建add()方法的栈帧,里面包含局部变量表(分配两个int槽位,存储传入的a、b的拷贝值),操作数栈(计算a+b的结果,并暂存于此)。在add方法结束之后,弹出add方法的栈帧,返回值压入main方法的操作数栈。

​栈内存变量状态快照

代码执行位置栈帧局部变量表槽位(索引)存储内容
int a, b;main0 (args)方法参数(未使用)
1 (a)int初始值0
2 (b)int初始值0
Scanner sc = ...main3 (sc)指向堆中Scanner的引用
a = sc.nextInt()main1 (a)键盘输入值(如5
b = sc.nextInt()main2 (b)键盘输入值(如3
add(a, b)内部add0 (a)maina的值拷贝
1 (b)mainb的值拷贝

 在弄清楚栈之后,我们要开始介绍程序计数器了

3、程序计数器

​​ 程序计数器(Program Counter Register)是JVM运行时数据区中一个非常核心但容易被忽视的组件。它的作用可以用一句话概括:​

记录当前线程正在执行的字节码指令的地址(行号)​,相当于代码执行的“书签”,确保线程切换或方法调用后能准确恢复到执行位置。

​程序计数器的作用​

1. ​线程执行的“导航仪”​

  • 每个线程独立拥有一个程序计数器,互不干扰。
  • 存储的是下一条待执行指令的地址​(字节码的行号偏移量)。
    • 例如:当前执行到main方法的第5行字节码,PC寄存器就保存5

2. ​方法调用时的“存档点”​

  • 当线程调用新方法(如add(a, b))时:
    1. PC寄存器会暂存当前方法的执行位置(如main方法的第10行)。
    2. 切换到新方法的起始指令地址(如add方法的第0行)。
    3. 方法返回时,根据PC寄存器保存的地址恢复执行。

3. ​Native方法的特殊处理

  • 如果线程执行的是native方法(如JNI调用),PC寄存器的值为undefined
    • 因为native方法的执行由本地代码(如C++)控制,不在JVM字节码范围内。

4.​为什么需要程序计数器?​

  • 线程切换恢复​:CPU时间片轮转时,线程可能被挂起,PC寄存器确保恢复后继续执行正确位置。假设线程A执行compute()PC=6时被挂起,线程B开始执行。当线程A恢复时,程序计数器会准确恢复到PC=6,继续执行iadd指令。
  • 方法调用/返回​:嵌套调用方法时(如A→B→C),PC寄存器保存调用链的返回路径。
  • 避免指令混乱​:无PC寄存器时,多线程执行可能导致代码位置错乱。

5.​与操作数栈的区别

程序计数器操作数栈
只存指令地址(数字),不存数据存方法执行的临时数据(如a+b的结果)
线程切换依赖它线程切换无关
永远不溢出可能栈溢出(StackOverflowError

​以我们上面的那个程序为例:

public static void main(String[] args) {int a = sc.nextInt();  // 假设字节码行号10int b = sc.nextInt();  // 行号15int result = add(a, b); // 行号20
}
  1. 线程执行到int a = sc.nextInt()时,PC寄存器保存10
  2. 调用nextInt()方法时,PC寄存器暂存15(下一条指令地址),跳转到nextInt的字节码起始位置。
  3. nextInt执行完毕返回后,PC恢复为15,继续执行。​

唯一无OOM的区域​:程序计数器是JVM规范中唯一不会发生OutOfMemoryError的区域。

线程私有​:每个线程独立存储,生命周期与线程相同。

性能关键​:JIT编译器会优化PC寄存器的使用,减少指令定位开销。

程序计数器虽然不直接存储业务数据,但它是JVM多线程执行和指令顺序控制的基础,类似于CPU中的指令指针(EIP/RIP)。

3、堆

JVM的堆内存(Heap Memory)是Java程序运行时数据区域中最重要的部分之一,主要用于存储对象实例和数组。堆内存是所有线程共享的内存区域,在JVM启动时创建。

以下是堆的内存结构图

堆内存的主要特点

  1. 对象存储区域​:几乎所有通过new关键字创建的对象实例都存储在堆中
  2. 垃圾回收的主要区域​:GC(垃圾收集器)主要管理堆内存
  3. 线程共享​:所有线程共享堆内存
  4. 动态分配​:堆的大小可以在JVM启动时指定,也可以动态扩展

堆内存的分代结构

现代JVM通常将堆内存划分为几个不同的代(Generation),以便更高效地进行垃圾回收:

1. 年轻代(Young Generation)

  • Eden区​:新创建的对象首先分配在Eden区
  • Survivor区​:分为From Survivor和To Survivor两个区域,用于存放从Eden区经过GC后存活的对象
  • 年轻代使用复制算法进行垃圾回收(Minor GC)

2. 老年代(Old Generation/Tenured Generation)

  • 存放长期存活的对象
  • 当对象在年轻代经历一定次数的GC后仍然存活,会被晋升到老年代
  • 老年代使用标记-清除或标记-整理算法进行垃圾回收(Major GC/Full GC)

3. 永久代/元空间(PermGen/Metaspace)

  • Java 8之前称为永久代(PermGen),Java 8及以后改为元空间(Metaspace)
  • 存储类元数据、方法区信息等
  • 元空间使用本地内存(Native Memory)而非堆内存

堆内存相关参数

  • -Xms:初始堆大小
  • -Xmx:最大堆大小
  • -Xmn:年轻代大小
  • -XX:NewRatio:老年代与年轻代的比例
  • -XX:SurvivorRatio:Eden区与Survivor区的比例

堆内存溢出

当堆内存不足时,会抛出OutOfMemoryError错误,常见原因包括:

  • 内存泄漏
  • 堆大小设置不合理
  • 对象生命周期过长

4、方法区(元空间)

方法区(Method Area)是JVM规范定义的一个逻辑内存区域,在Java 8之前被称为永久代(PermGen)​,从Java 8开始被元空间(Metaspace)​取代。

核心概念

  1. 存储内容​:

    • 类元数据(Class metadata):类的结构信息(字段、方法、构造器等)
    • 运行时常量池(Runtime Constant Pool)
    • 静态变量(Static variables)
    • 即时编译器(JIT)编译后的代码
    • 方法字节码
  2. 与堆的关系​:

    • 在Java 7及之前:方法区是堆的逻辑部分,物理上位于堆内存中
    • 从Java 8开始:元空间使用本地内存(Native Memory),不再属于堆内存

永久代 vs 元空间

特性永久代(PermGen)元空间(Metaspace)
位置JVM堆内存的一部分本地内存(Native Memory)
大小限制-XX:MaxPermSize限制默认只受系统可用内存限制
垃圾回收Full GC时回收可单独触发元空间GC
内存溢出java.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: Metaspace
调优参数-XX:PermSize, -XX:MaxPermSize-XX:MetaspaceSize, -XX:MaxMetaspaceSize

元空间关键特性

  1. 动态扩展​:

    • 默认情况下,元空间会根据应用需求动态调整大小
    • 避免了永久代固定大小导致的OutOfMemoryError
  2. 类元数据生命周期​:

    • 与类加载器(ClassLoader)生命周期绑定
    • 当类加载器被回收时,其加载的类元数据也会被回收
  3. 内存管理​:

    • 使用mmap而非malloc来分配内存
    • 采用块(Chunk)分配策略,提高内存利用率

相关JVM参数

# 初始元空间大小(并非初始就分配,而是首次GC的阈值)
-XX:MetaspaceSize=64M# 最大元空间大小(默认基本无限制)
-XX:MaxMetaspaceSize=256M# 元空间扩容时增加的幅度(默认约20%)
-XX:MinMetaspaceFreeRatio=40
-XX:MaxMetaspaceFreeRatio=70# 启用类元数据的并行卸载(JDK 12+)
-XX:+ClassUnloadingWithConcurrentMark

常见问题

  1. 元空间内存泄漏​:

    • 通常由未关闭的类加载器引起
    • 常见于动态生成类(如使用ASM、CGLIB)的应用
  2. 调优建议​:

    • 监控元空间使用情况(JVisualVM、JConsole等工具)
    • 对于大量使用动态代理的应用,适当增加MaxMetaspaceSize
    • 避免创建过多类加载器
  3. 性能影响​:

    • 元空间GC会导致应用暂停(但比永久代Full GC影响小)
    • 过大的元空间可能影响Native Memory其他组件的使用

5、本地方法栈(Native Method Stack)

本地方法栈是JVM内存结构中一个专门为本地方法(Native Method)​服务的内存区域,与Java虚拟机栈类似但服务于不同的目的。

核心概念

  1. 基本定义​:

    • 为JVM运行本地方法(Native Method)​服务的内存区域
    • 每个线程在调用本地方法时会创建独立的本地方法栈
    • 存储本地方法的调用状态、参数、局部变量等

举例:

//定义一个线程对象并开启
Thread t = new Thread(); 
t.start();                

那么在这个start()方法的底层就有一个本地方法:

  1. 与虚拟机栈的区别​:

    特性Java虚拟机栈本地方法栈
    服务对象Java方法本地方法(用native修饰的方法)
    实现规范JVM规范明确要求由JVM实现者自行决定
    语言类型Java语言实现通常用C/C++实现
    异常类型StackOverflowError/OutOfMemoryError由操作系统决定

关键特性

  1. 内存分配​:

    • 线程私有,生命周期与线程相同
    • 大小可以通过-Xss参数设置(与Java虚拟机栈共享同一参数)
    • 在HotSpot JVM实现中,本地方法栈和虚拟机栈是合二为一的
  2. 运行机制​:

    • 当线程调用native方法时:
      1. 在本地方法栈中创建栈帧
      2. 动态链接到本地方法接口(JNI)
      3. 执行本地方法实现(通常位于.dll.so文件中)
      4. 返回结果后栈帧出栈
  3. 重要限制​:

    • 栈深度限制(可能抛出StackOverflowError)
    • 内存分配失败(可能抛出OutOfMemoryError)
    • 本地方法执行错误可能导致JVM崩溃(因为超出了JVM控制范围)

常见问题与调优

  1. StackOverflowError​:

    • 本地方法递归调用层次过深
    • 解决方案:增大栈大小(-Xss参数),或优化本地方法实现
  2. 内存泄漏​:

    • 本地方法中分配的内存未正确释放
    • 特别注意事项:通过JNI New创建的Java对象需要特别管理
  3. 性能调优​:

    # 设置线程栈大小(包括本地方法栈)
    -Xss1m# 对于大量使用本地方法的应用,可能需要增大该值
    -Xss2m
  4. 安全风险​:

    • 本地方法栈可能成为攻击入口(缓冲区溢出等)
    • 建议:对关键本地方法进行严格的安全审计

注意事项

  1. HotSpot JVM的实现特点:

    • 在主流JVM实现(如HotSpot)中,本地方法栈和Java虚拟机栈是合并的
    • 通过-Xoss参数设置本地方法栈大小的功能在HotSpot中无效
  2. 平台差异性:

    • 不同操作系统对本地方法栈的支持可能有差异
    • 32位/64位系统的默认栈大小不同
  3. 调试建议:

    • 使用-XX:+PrintFlagsFinal查看实际栈大小
    • 对于本地方法问题,需要结合系统级调试工具(如gdb)

http://www.hkcw.cn/article/tBsQmkcBEF.shtml

相关文章

突破铁芯CT局限:罗氏线圈的“无磁饱和”技术深度解读

罗氏线圈互感器:关键应用场景解析 罗氏线圈(Rogowski Coil)互感器以其独特的性能优势,成为测量交流电流(尤其适用于变化快、幅度大或频率范围广的电流)的理想选择。其核心优势在于宽频带、无磁饱和、尺寸灵…

Oracle数据仓库在医院的应用场景

2025年3月28日,我作为会议组织者,在宁波组织了数据仓库在医院的应用场景,会议主要议题如下: 1、解析医院多源异构数据(HIS/LIS/EMR/PACS)实时整合的技术方案 2、Oracle exadata在构建全院级数据仓库的性能优…

JavaScript引用类型

引用类型概述 变量值的两种存储方式 1原始值 2引用值 基本数据类型 5种基本数据类型都是按照原始值的方式来存储的,也叫原始数据类型。 undefined表示没有存储值,也是一种基本数据类型;null表示存储了值,但是空指针&#xff…

实战项目7(10)【待更新......】

任务场景一 按照下图完成网络拓扑搭建和配置 任务要求: 按照以上网络拓扑,将直连路由重发布至OSPF动态路由信息中,实现终端PC间可以正常访问。 【R1】配置 【R2】配置 【R3】配置 任务场景二 按照下图完成网络拓扑搭建和配置 任务要求&…

C# 将HTML文档、HTML字符串转换为图片

在.NET开发中,将HTML内容转换为图片的需求广泛存在于报告生成、邮件内容存档、网页快照等场景。Free Spire.Doc for .NET作为一款免费的专业文档处理库,无需Microsoft Word依赖,即可轻松实现这一功能。本文将深入解析HTML文档和字符串转图片两…

MySql(八)

目录 查询 1)准备一张表 2)准备数据 3)查询表中的数据 4)只查表中的某些列 5)为列起别名 使用 as 不使用as 查询 1)准备一张表 CREATE table role( roleid INT PRIMARY KEY AUTO_INCREMENT, rolename VA…

Redis Sorted Set 深度解析:从原理到实战应用

Redis Sorted Set 深度解析:从原理到实战应用 在 Redis 丰富的数据结构家族中,Sorted Set(有序集合)凭借独特的设计和强大的功能,成为处理有序数据场景的得力工具。无论是构建实时排行榜,还是实现基于时间的…

95套HTML高端大数据可视化大屏源码分享

概述​​ 在大数据时代,数据可视化已成为各行各业的重要需求。这里精心整理了95套高端HTML大数据可视化大屏源码,这些资源采用现代化设计风格,可帮助开发者快速构建专业的数据展示界面。 ​​主要内容​​ ​​1. 设计风格与特点​​ 采用…

Python 中的空间 KDE 图

数据:布里斯班致命车祸 我将使用昆士兰道路车辆事故数据集,该数据集可从昆士兰开放数据门户获取。该数据集提供了昆士兰州2001年1月1日至2023年11月30日期间所有已报告的道路交通事故的地点和特征信息。 我只想关注致命事故,所以唯一需要关…

变焦位移计:机器视觉如何克服人工疲劳与主观影响?精准对结构安全实时监测

变焦视觉位移监测与人工监测的对比 人工监测是依靠目测检查或借助于全站仪,水准仪,RTK等便携式仪器测量得到的信息,但是随着整个行业的发展,传统的人工监测方法已经不能满足监测需求,从人工监测到自动化监测已是必然趋…

【JavaWeb】Maven、Servlet、cookie/session

目录 5. Maven6. Servlet6.1 Servlet 简介6.2 HelloServlet6.3 Servlet原理6.4 Mapping( **<font style"color:rgb(44, 44, 54);">映射 ** )问题6.5 ServletContext6.6 HttpServletResponse<font style"color:rgb(232, 62, 140);background-color:rgb(…

【Day40】

DAY 40 训练和测试的规范写法 知识点回顾&#xff1a; 彩色和灰度图片测试和训练的规范写法&#xff1a;封装在函数中展平操作&#xff1a;除第一个维度batchsize外全部展平dropout操作&#xff1a;训练阶段随机丢弃神经元&#xff0c;测试阶段eval模式关闭dropout 作业&#x…

GEARS以及与基础模型结合

理解基因扰动的反应是众多生物医学应用的核心。然而&#xff0c;可能的多基因扰动组合数量呈指数级增长&#xff0c;严重限制了实验探究的范围。在此&#xff0c;图增强基因激活与抑制模拟器&#xff08;GEARS&#xff09;&#xff0c;将深度学习与基因-基因关系知识图谱相结合…

【C++】入门基础知识(1.5w字详解)

本篇博客给大家带来的是一些C基础知识&#xff01; &#x1f41f;&#x1f41f;文章专栏&#xff1a;C &#x1f680;&#x1f680;若有问题评论区下讨论&#xff0c;我会及时回答 ❤❤欢迎大家点赞、收藏、分享&#xff01; 今日思想&#xff1a;微事不通&#xff0c;粗事不能…

[SWPUCTF 2023 秋季新生赛]Classical Cipher203分古典密码Base家族栅栏密码

下载附件解压得到txt文件 得到信息 U2FsdGVkX19aQNEomnRqmmLlI9qJkzr0pFMeMBF99ZDKTF3CojpkTzHxLcu/ZNCYeeAV3/NEoHhpP5QUCK5AcHJlZBMGdKDYwko5sAATQ 用在线网站进行解密 解密得到 TGhmYlMlXXNwX2BTb3NoQWcye1VweSRfcXEGdmBheDx0I1BkMXdfXG0ldzdbGBy 栅栏密码用在线网站进行…

Unity 中实现首尾无限循环的 ListView

之前已经实现过&#xff1a; Unity 中实现可复用的 ListView-CSDN博客文章浏览阅读5.6k次&#xff0c;点赞2次&#xff0c;收藏27次。源码已放入我的 github&#xff0c;地址&#xff1a;Unity-ListView前言实现一个列表组件&#xff0c;表现方面最核心的部分就是重写布局&…

【提升工作效率的小工具】截图软件Snipaste

1.F1截图&#xff0c;F3钉在桌面上 2.小技巧 纯文本复制后&#xff0c;F3钉在桌面上&#xff0c;鼠标右键&#xff0c;点击复制纯文本&#xff0c;可以直接再次复制 shift鼠标双击截图&#xff0c;可以缩略显示不占位置&#xff0c;重复操作就是展开截图。 隐藏/显示所有贴图…

极刻云搜2.0-强大的蓝奏搜索引擎以及智能网址导航

【&#x1f389; 重磅发布】极刻云搜 2.0 正式上线&#xff01; &#x1f680; 核心升级&#xff1a; ✨ 界面全面焕新&#xff1a; 视觉更美观&#xff0c;操作更流畅&#xff0c;体验显著提升。 &#x1f50d; 搜索能力升级&#xff1a; 在原有站内搜索&#xff08;覆盖全站…

无人机视角海上漂浮物检测与人员救援检测数据集VOC+YOLO格式2903张6类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2903 标注数量(xml文件个数)&#xff1a;2903 标注数量(txt文件个数)&#xff1a;2903 …

cocosCreator 1.8 升级到 2.4

现在负责的一个运营中的商业项目&#xff0c;使用的是 cocosCreator1.8&#xff0c;之前没有做好设计&#xff0c;所以东西都是直接加载在内存中的&#xff0c;到了现在性能问题逐渐暴露出来&#xff0c;讨论之后想进行引擎升级&#xff0c;升级到cocosCreator 2.4。 官方的升…