当前位置: 首页 > news >正文

JVM内存模型与垃圾回收机制分析

1、介绍

JVM内存模型涉及运行代码,即运行代码产生的垃圾数据需要回收,堆是对象生存的核心战场,GC 主要在此区域运作,GC解决内存有限性、避免手动管理风险、动态回收垃圾、保障性能与稳定性,让开发者从内存管理中解放,专注于业务逻辑实现

2、JVM运行时的数据区由哪些组成的

1、组成结构

+-------------------+
|   方法区(Method)  | <--- JDK8+:元空间(Metaspace)
|-------------------|
|(Heap)     | <--- 对象实例、数组(GC主战场)
|-------------------|
| 虚拟机栈(VM Stack)| <--- 线程私有:栈帧(局部变量表/操作数栈/动态链接/返回地址)
|-------------------|
| 本地方法栈(Native)| <--- JNI调用本地方法(C/C++|-------------------|
|  程序计数器(PC)   | <--- 当前线程执行的字节码行号指示器
+-------------------+

2、堆和栈有什么区别

堆,是线程共享的,用来存储对象的实例,比如Object obj = new Object()出来的对象实例。也存字符串常量的具体值。由GC统一进行管理。

栈,是线程私有的,用来存储基本类型变量和对象的引用信息,比如obj的引用信息。栈信息是自动的分配和释放。

3、方法区的作用是什么,JDK8+之后发生了什么重大变化么

方法区在JDK7中称之为永久代,受JVM最大堆内存限制大小,在JDK8之后名称变成了Metaspace元空间,并且使用的是本地操作系统的本地内存,受操作系统本地可用内存大小限制。存储的内容主要有如下:类的元信息如类全限定名、访问修饰符、字段描述、方法描述

为什么元空间要取代永久代呢?

主要解决永久代的内存溢出性能调优问题。永久代受限于JVM堆大小,而元空间使用本地操作系统内存,避免了Full GC对元数据回收的影响。同时分离元数据与堆内存,简化了JVM架构。

4、元空间会内存泄漏么?怎么排查

​ 元空间也会出现内存泄漏问题,主要原因是由于代码设计不合理每次运行时都新建一个类加载器并加载了一些类信息放到元空间,并且这个类加载器(自定义或框架的)一直存在强引用,导致GC无法将类加载器回收,其加载的所有元数据信息永久驻留在元空间当中,并且一直增大,最终会导致元空间内存溢出风险。

​ 线上可以通过Arthas工具,执行classloader -t dashboard,可查看跟踪ClassLoader 内存及使用情况。也可以通过jstat -gcmetacapacity <PID>观察元空间使用量是否只增不减,且Full GC后不下降。”

如果发现了问题可以修改代码将自定义的类加载器进行缓存处理,避免每次都新建类加载器。用完类加载器其相关的类确保无强引用,这样GC可回收。修复上线之后可观察GC元空间大小是否变小即可。
除了优化代码生产环节最好通过配置-XX:MaxMetaspaceSize=512m限制元空间上限,监控类加载情况,控制风险。

5、字符串常量是怎么存储的

  • JDK6:永久代(方法区)
  • JDK7移动到堆中(通过-XX:StringTableSize调整大小)
  • JDK8+:仍在堆中(元空间只存类元信息,不存字符串常量)

🌰 示例:String s = "abc""abc"中,而String.class的元数据在元空间

3、JVM垃圾回收机制分析(GC)

1、垃圾回收的流程

1、判断可回收对象

通过可达性分析对象是否存活,GC_Roots从栈变量寻找关联对象(如对象A引用了对象C,对象B也在栈变量里),假设对象E不在这个GC_Roots关联链中,判断对象E可回收。

GC_Roots
对象A
对象B
对象C
不可达
对象E
2、清除可回收对象

使用垃圾回收器,回收不可达的对象。主要垃圾回收器如下:

回收器区域算法启动参数特点适用场景
Serial新生代标记-复制-XX:+UseSerialGC单线程STW(Stop-The-World),**旧版 JDK(≤8)**可通过 分代组合 配置不同回收器,适合客户端模式(如桌面应用)
ParNew新生代标记-复制-XX:+UseParNewGCSerial 的多线程版本,**旧版 JDK(≤8)**可通过 分代组合 配合CMS老年代 回收器使用。
Parallel Scavenge新生代标记-复制-XX:+UseParallelGC多线程吞吐量优先,**旧版 JDK(≤8)**可通过 分代组合 配合Parallel Old老年代回收器使用,适合后台计算型应用
Serial Old老年代标记-整理-XX:+UseSerialOldGCSerial 的老年代版本,**旧版 JDK(≤8)**可通过 分代组合 配置不同回收器,适合客户端模式(如桌面应用)
Parallel Old老年代标记-整理-XX:+UseParallelOldGC多线程吞吐量优先,**旧版 JDK(≤8)**可通过 分代组合 配合Parallel Scavenge新生代回收器使用,适合后台计算型应用
CMS (ConcMarkSweep)老年代标记-清除-XX:+UseConcMarkSweepGC低延迟优先,并发收集(减少 STW),**旧版 JDK(≤8)**可通过 分代组合 配置不同回收器,JDK 14+ 已移除 CMS。适合Web 服务、响应敏感系统
G1(Garbage-First)全堆分区域标记-整理-XX:+UseG1GC可预测停顿时间,兼顾吞吐与延迟,JDK 9+ 默认
ZGC全堆升级版标记-复制(染色指针+读屏障)-XX:+UseZGC超大内存、低延迟系统,TB 级堆,JDK 17+ 推荐使用 ,升级版的标记复制算法体现在消除了转移复制导致的停顿STW,而通过染色指针+读屏障实现并发转移,仅仅需要极短的停顿即可,适合金融交易、实时游戏、大数据分析等系统。
Shenandoah全堆-XX:+UseShenandoahGC与 ZGC 类似,RedHat 贡献

一个JVM可配置多个垃圾回收器么?在JDK8之前使用分代组合,新生代和老年代可配置不同的垃圾回收器。在JDK9+推荐全堆垃圾回收器。消除了分代组合带来的复杂性。

  • 标记-清除(Mark-Sweep)

如CMS垃圾回收器(JDK 14+ 已移除 CMS)采用的标记清除法。并发标记所有可达对象。清除未标记的对象。此方式容易造成内存碎片化(后续需 Full GC 整理),对象越多越慢,如果垃圾产生的速度大于回收速度时,会退化为标记整理法 Serial Old。并发多还会占用线程资源。

具体流程如下

  1. 初始标记(STW):标记 GC Roots 直接关联的对象。
  2. 并发标记:遍历对象图(与用户线程并发)。
  3. 重新标记(STW):修正并发标记期间的变动。
  4. 并发清除:清理垃圾(并发执行)。
  • 标记-复制(Copying)

将内存分为两块(如 EdenSurvivor)。将存活对象复制到另一块内存,清空当前块。

此方式内存不会造成碎片化,效率也比较高,但是导致内存的利用率变低仅 50%。

但是在超大内存、低延迟系统,TB 级堆,JDK 17+ 推荐使用 ZGC垃圾回收器。不在担心内存不够用,而且相比于传统的标记-复制算法,ZGC升级了的标记复制算法体现在消除了转移复制导致的停顿STW,而通过染色指针+读屏障实现并发转移,仅仅需要极短的停顿即可,适合金融交易、实时游戏、大数据分析等系统。

  • 标记-整理(Mark-Compact)

标记所有可达对象。将存活对象向一端移动,清理边界外内存。

此方式不会造成碎片化(适用于老年代),但是移动成本比较高

G1垃圾回收器标记-整理进行优化,针对于整个堆,它将堆分成多个区域(默认2048个),通过停顿预测模型计算每个区域的回收价值(回收时间+释放空间),优先回收最有价值的区域。

  • 分代收集(Generational Collection)

⚠️ 注意:分代手机已不推荐使用,JDK9+推荐使用G1、ZGC、Shenandoah 全堆回收器,会统一管理新生代和老年代,不能与其他回收器组合
例如:-XX:+UseG1GC 会覆盖所有分代配置。

分代收集可了解如下:

​ 按对象生命周期划分内存区域(新生代、老年代),对不同区域使用不同算法。

新生代:对象存活率低 → 复制算法Eden + 2个 Survivor 区)。

老年代:对象存活率高 → 标记-清除标记-整理

同一个内存区域不可以同时配置多个垃圾回收器,为不同区域配置不同的回收器,这是JVM分代垃圾回收机制的核心设计。

JVM 将堆内存分为 新生代(Young Generation)老年代(Old Generation),允许为两者独立选择回收器:

  1. 新生代回收器
    Serial, ParNew, Parallel Scavenge
  2. 老年代回收器
    Serial Old, Parallel Old, CMS

组合示例如下:

# 新生代用 ParNew + 老年代用 CMS(经典组合)
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC# 新生代用 Parallel Scavenge + 老年代用 Parallel Old(吞吐量优先)
-XX:+UseParallelGC -XX:+UseParallelOldGC

2、Minor GC vs Full GC 的区别?

当年轻代的Eden区满时,触发Minor GC 回收新生代的区域(Eden + Survivor)

当老年代空间不足或元空间不足或代码主动System.gc(),触发Full GC 回收整个堆(新生代 + 老年代 + 元空间)

G1虽然是全堆垃圾回收器,仍有类似 Minor GC 的行为(Young GC),但物理上不要求连续的新生代内存,且回收机制与传统分代回收器不同。

ZGC:完全不分代,无 Minor GC,ZGC 彻底移除了分代设计(截至 JDK 21),采用 全堆并发回收, 通过 并发复制算法 直接管理全堆,避免分代带来的复杂性和停顿。

3、对象如何晋升到老年代

  • 年龄阈值:对象在 Survivor 区每熬过一次 Minor GC 年龄 +1,达到阈值(默认 15)进入老年代。
  • 大对象直接进入老年代(如长数组)。
  • Survivor 区空间不足时,存活对象直接进入老年代。
http://www.dtcms.com/a/263863.html

相关文章:

  • 【java链式调用流操作】
  • Python实现NuScenes数据集可视化:从3D边界框到2D图像的投影原理与实践
  • mac部署dify
  • 笔记/计算机网络
  • 【数据结构】 排序算法
  • beego打包发布到Centos系统及国产麒麟系统完整教程
  • 【文件读取】open | with | as
  • 实体类JavaBean
  • 到底什么是“数字化”?数字化的本质是什么?
  • 从输入到路径:AI赋能的地图语义解析与可视化探索之旅(2025技术全景)
  • 边截图边操作?试试 Snipaste 的浮动贴图功能
  • adc模数转换器
  • Gartner《Choosing Event Brokers to Support Event-DrivenArchitecture》心得
  • OSE3.【Linux】练习:编写进度条及pv命令项目中的进度条函数
  • Postman - API 调试与开发工具 - 标准使用流程
  • 搜索与回溯算法(基础算法)
  • 华为交换机堆叠与集群技术深度解析附带脚本
  • Golang的并发编程实践总结
  • 【pathlib 】Python pathlib 库教程
  • 成都芯谷金融中心文化科技园:打造区域科技活力
  • nginx配置websocket
  • 用java,把12.25.pdf从最后一个点分割,得到pdf
  • Elastic 构建 Elastic Cloud Serverless 的历程
  • CertiK《Hack3d:2025年第二季度及上半年Web3.0安全报告》(附报告全文链接)
  • 61、【OS】【Nuttx】【构建】向量表
  • Redis-7.4.3-Windows-x64下载安装使用
  • 浅谈Docker Kicks in的应用
  • ‌Webpack打包流程
  • 为什么时序数据库IoTDB选择Java作为开发语言
  • Milvus docker-compose 部署