JVM内存与GC机制全景深度剖析:从对象诞生到垃圾回收的完整生命周期

news/2025/12/9 23:17:47/文章来源:https://www.cnblogs.com/RTower/p/19328684

本文将从对象完整生命周期的视角,系统性地阐述JVM内存管理和垃圾回收机制。你将看到对象如何出生、如何存活、如何晋升,以及最终如何被回收的完整过程。

核心叙事线:一个对象的"人生旅程"

  • 出生:在堆内存中分配(Eden区)
  • 成长:在Survivor区中经历多次GC考验
  • 成熟:晋升到老年代安享晚年
  • 终结:被GC回收,生命结束
  • 底层支撑:内存模型如何保证这个过程的线程安全

第一部分:对象的诞生与内存分配

1.1 内存的舞台:运行时数据区全景

在对象出生之前,我们先看看JVM为它准备了什么样的舞台。

image

1.2 对象的创建过程(逐步分解)

Object obj = new Object();

这行简单代码背后,JVM执行了复杂的操作:

  1. 类加载检查:检查new指令的参数是否能在常量池中定位到类的符号引用,并检查类是否已被加载、解析和初始化。

  2. 内存分配:在堆中为新生对象分配内存。分配方式有两种:

    • 指针碰撞:内存规整时,移动指针划分内存
    • 空闲列表:内存不规整时,从空闲列表中找到足够大的空间
  3. 内存空间初始化:将分配到的内存空间都初始化为零值(不包括对象头)

  4. 设置对象头:存储对象的元数据(哈希码、GC分代年龄、锁状态等)

  5. 执行 <init>****方法:按照程序员的意愿进行初始化

image

其中Mark Word 在32位虚拟机中结构如下:

image

在64位虚拟机中结构如下:

image

1.3 内存分配策略

  1. 优先在Eden区分配:大多数新对象在Eden区分配
  2. 大对象直接进入老年代:避免在Eden区和Survivor区之间大量复制
  3. 长期存活的对象进入老年代:对象年龄计数器达到阈值(默认15)时晋升
  4. 动态年龄判定:Survivor区中相同年龄所有对象大小超过Survivor空间一半时,年龄≥该年龄的对象直接晋升

第二部分:对象的存活与GC算法

2.1 判断对象存活的算法

引用计数法(Python采用):

  • 优点:实现简单,判断高效
  • 缺点:无法解决循环引用问题

image

可达性分析算法(Java采用):

  • 从GC Roots对象作为起点,向下搜索,走过的路径称为"引用链"
  • 如果一个对象到GC Roots没有任何引用链相连,则判定为可回收

GC Roots包括

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象
  • Java虚拟机内部的引用(基本类型对应的Class对象、系统类加载器等)
  • 被同步锁持有的对象

image

2.2 引用类型:强、软、弱、虚

  1. 强引用:普通的Object obj = new Object(),永远不会被GC
  2. 软引用:内存不足时会被回收,适合做缓存
  3. 弱引用:下次GC时就会被回收
  4. 虚引用:无法通过虚引用获取对象,主要用于跟踪对象被回收的状态

1. 强引用
  • 创建语句:就是普通的对象赋值。

    Object obj = new Object(); // obj就是一个强引用
    String str = "Hello";     // str也是一个强引用
    
  • 核心特性

    • 只要强引用关系存在,垃圾收集器就永远不会回收掉被引用的对象。
    • 当内存不足时,JVM 会抛出 OutOfMemoryError错误,也不会通过回收强引用的对象来释放内存。
  • 回收时机:当对象没有任何强引用指向它时(例如将 obj设置为 null,或者 obj离开了作用域),它才变得可被回收。

  • 典型使用场景:我们日常开发中 99% 的代码都在使用强引用。它是构成程序骨架的默认引用类型。


2. 软引用
  • 创建语句:使用 java.lang.ref.SoftReference类。

    // 创建一个强引用的对象
    Object strongRef = new Object();
    // 用一个强引用对象来创建一个软引用
    SoftReference<Object> softRef = new SoftReference<>(strongRef);// 通常也会配合引用队列(ReferenceQueue)使用
    ReferenceQueue<Object> queue = new ReferenceQueue<>();
    SoftReference<Object> softRefWithQueue = new SoftReference<>(strongRef, queue);// 取消强引用,此时只剩下softRef这个软引用
    strongRef = null;// 需要时尝试获取对象
    Object target = softRef.get(); // 如果对象未被回收,则target不为null
    if (target != null) {// 对象还存在,可以使用
    } else {// 对象已被回收,需要重新创建
    }
    
  • 核心特性:在系统内存不足时,垃圾收集器会回收掉只被软引用指向的对象。回收发生在 OOM 错误被抛出之前

  • 回收时机:内存不足时。

  • 典型使用场景:非常适合实现内存敏感的缓存

    • 图片缓存:将大量图片数据放在软引用缓存中。当应用内存紧张时(例如在后台运行,系统需要内存),缓存会被自动清除,避免 OOM。当用户再次回到应用时,虽然缓存可能没了,但可以从磁盘或网络重新加载。
    • 计算结果缓存:缓存一些计算成本高但非必需的结果。

3. 弱引用
  • 创建语句:使用 java.lang.ref.WeakReference类。

    Object strongRef = new Object();
    WeakReference<Object> weakRef = new WeakReference<>(strongRef);// 取消强引用
    strongRef = null;// 强制执行GC(仅用于演示,生产代码中不要轻易调用)
    System.gc();// GC后,weakRef.get()有很大概率返回null
    if (weakRef.get() == null) {System.out.println("对象已被GC回收");
    }
    
  • 核心特性无论内存是否充足,只要发生了垃圾收集,并且对象只被弱引用指向,那么这个对象就会被回收。它的生命周期比软引用更短。

  • 回收时机:下一次垃圾收集发生时。

  • 典型使用场景

    • WeakHashMap****的键WeakHashMap的键是弱引用。当某个键对象除了在 WeakHashMap中被弱引用外,没有其他强引用时,下次GC这个键值对就会被自动移除。常用于存储对象的元数据,当对象本身失效时,元数据自动清理。
    • 防止内存泄漏的辅助结构:例如,在某些监听器模式下,可以用弱引用来保存监听器,这样当主对象不再使用时,监听器不会因为被缓存而无法回收。但使用时要非常小心,因为监听器可能在任何时候被GC掉。
    • ThreadLocal 中的 ThreadLocalMap****的键 也使用了弱引用来避免内存泄漏(但值仍然是强引用,所以正确使用后需要手动 remove())。

4. 虚引用
  • 创建语句:使用 java.lang.ref.PhantomReference类。必须和引用队列(ReferenceQueue)联合使用。

    Object strongRef = new Object();
    ReferenceQueue<Object> queue = new ReferenceQueue<>();
    PhantomReference<Object> phantomRef = new PhantomReference<>(strongRef, queue);// 取消强引用
    strongRef = null;// 此时,phantomRef.get() 永远返回 null,无法通过它获取对象// 执行GC后,对象被回收,JVM会将虚引用对象phantomRef本身加入到队列queue中
    System.gc();// 检查引用队列,如果有元素出队,说明被监控的对象被回收了
    Reference<?> ref = queue.poll();
    if (ref != null) {System.out.println("检测到对象被回收,可以进行后续清理工作");// 通常在这里执行一些堆外内存释放等收尾操作
    }
    
  • 核心特性

    1. 无法通过虚引用获取对象实例,即 get()方法总是返回 null
    2. 唯一作用是利用引用队列跟踪对象被垃圾回收的准确时刻
    3. 虚引用本身比它所引用的对象更“坚强”,需要显式地将其从队列中取出后,它本身才会被GC。
  • 回收时机:对象被GC的最终阶段。可以认为一个对象设置了虚引用,就等于被“判了死刑”,但虚引用就像刑场外的记者,它的存在让你能准确知道“行刑”(对象被回收)这个事件发生了。

  • 典型使用场景

    • 管理堆外内存(如 NIO 的 DirectByteBuffer): 这是最经典的用途。JVM 的堆内存由 GC 管理,但通过 Unsafe或 NIO 分配的堆外内存 GC 管不了。我们可以在 Java 堆中创建一个很小的对象(如 DirectByteBuffer)来代表一块很大的堆外内存,并为这个对象关联一个虚引用。当这个小的 Java 对象被 GC 回收时(意味着没有强引用再指向它),通过虚引用队列的通知,我们就可以知道此时应该去释放对应的堆外内存,从而避免堆外内存泄漏。

总结对比

引用类型 创建方式 垃圾回收时机 生存时间(强度) 用途
强引用 Object obj = new Object() 永远不会 最强 程序默认状态,所有正常对象创建
软引用 SoftReference softRef = new SoftReference(obj) 内存不足 较强 实现内存敏感缓存(如图片缓存)
弱引用 WeakReference weakRef = new WeakReference(obj) 下一次GC 较弱 WeakHashMap、防止内存泄漏的辅助缓存
虚引用 PhantomReference phantomRef = new PhantomReference(obj, queue) 对象被回收的最终时刻 最弱(无法获取对象) 跟踪对象被回收的事件,用于堆外内存释放等收尾工作

第三部分:垃圾回收算法与实现

3.1 基础回收算法

标记-清除算法

  • 过程:先标记所有需要回收的对象,然后统一回收
  • 缺点:产生内存碎片,分配大对象时可能失败
  • img
  • 图片出处:https://www.cnblogs.com/trunks2008/p/15341715.html

复制算法

  • 过程:将内存分为两块,每次使用一块,将存活对象复制到另一块
  • 优点:没有碎片,实现简单
  • 缺点:内存利用率只有50%
  • img
  • 图片出处:https://www.cnblogs.com/trunks2008/p/15341715.html

标记-整理算法

  • 过程:标记存活对象,让所有存活对象向一端移动,然后清理边界外的内存
  • 优点:没有碎片问题
  • 缺点:移动对象成本高
  • img
  • 图片出处:https://www.cnblogs.com/trunks2008/p/15341715.html

3.2 分代收集理论:现代GC的基石

基于弱分代假说和强分代假说,堆内存被划分为:

  1. 新生代:对象朝生夕死,回收频繁

    • 采用复制算法
    • 比例:Eden:Survivor:Survivor = 8:1:1
  2. 老年代:对象存活率高,回收不频繁

    • 采用标记-清除标记-整理算法
  3. 跨代引用问题:老年代对象引用新生代对象,需要额外处理

img

  • 图片出处:https://www.cnblogs.com/trunks2008/p/15341715.html

3.3 分代GC完整流程

image


第四部分:现代垃圾回收器详解

4.1 回收器分类

分类 新生代回收器 老年代回收器 特点
串行 Serial Serial Old 单线程,STW时间长
并行 ParNew Parallel Old 多线程,吞吐量优先
并发 - CMS, G1, ZGC 低延迟优先

4.2 回收器多维度对比

维度 Serial Parallel CMS G1 ZGC Shenandoah
设计目标 单线程轻量级应用 吞吐量优先 低延迟(响应敏感) 平衡型(吞吐+低延迟) 超低延迟(亚毫秒级) 超低延迟(与堆大小无关)
停顿时间 高(STW) 中高(STW) 中低(部分STW) 低(可预测) 极低(<1ms) 极低(<1ms)
吞吐量 一般 中高 中高 中(读屏障开销) 中(写屏障开销较高)
是否并发 否(年轻代并行)
是否压缩/整理 是(标记-复制) 是(标记-复制) 否(标记-清除) 是(标记-整理) 是(并发压缩) 是(并发复制)
是否支持大堆内存 ✅(小堆) ✅(中等堆) ⚠️(碎片化限制) ✅(大堆) ✅(TB级) ✅(TB级)
是否商用可用 ✅(过时) ⚠️(JDK 9+废弃) ✅(JDK 9+默认) ✅(JDK 11+生产可用) ✅(OpenJDK/RedHat)
是否产生浮动垃圾 ✅(CMS问题) ❌(STW筛选回收) ❌(染色指针+STW) ❌(Brooks指针+STW)
核心技术 STW单线程 STW多线程 并发标记+清除 区域化+垃圾优先回收 染色指针+读屏障 Brooks指针+双屏障
适用场景 小型应用/嵌入式 批处理/计算密集型 响应敏感型系统 大内存服务/平衡型 金融/实时交易系统 分布式/跨平台场景
版本支持 JDK 1.3.1+ JDK 1.4.2+ JDK 1.4.1+(JDK 9废弃) JDK 7u4+(JDK 9默认) JDK 11+ JDK 12+

4.2 重要回收器深度解析

CMS(Concurrent Mark-Sweep)回收器

  • 目标:最短回收停顿时间
  • 过程:初始标记→并发标记→重新标记→并发清除
  • 缺点:产生内存碎片,对CPU资源敏感

G1(Garbage-First)回收器

  • 革命性变化:将堆划分为多个Region,优先回收价值最大的Region
  • 过程:初始标记→并发标记→最终标记→筛选回收
  • 可预测的停顿时间模型

ZGC和Shenandoah

  • 目标:亚毫秒级停顿时间
  • 关键技术:染色指针、读屏障
  • 几乎在所有停顿时间上都优于G1

image


第五部分:内存模型与GC的协同工作

6.1 并发的基石:JMM保证GC的正确性

GC过程中,JMM的关键作用:

  1. 安全点:GC发生时,所有线程必须到达一个安全点才能暂停
  2. 记忆集:解决跨代引用问题,避免全堆扫描
  3. 写屏障:在对象引用写入时执行额外操作,维护记忆集

6.2 实战案例:为什么GC需要Stop-The-World

// 在GC过程中,如果没有STW,可能发生:
// 线程A:读取对象O的字段f
// GC线程:移动对象O到新位置
// 线程A:使用字段f(此时对象已移动,可能访问到错误内存)// JMM通过STW保证在GC过程中对象引用关系不会变化

6.3 内存屏障与GC的协同

  • 读屏障:在读取引用前执行,用于并发标记(G1、ZGC)
  • 写屏障:在写入引用后执行,用于维护记忆集

完整生命周期案例:一个Web请求对象的旅程

让我们通过一个具体案例,完整理解对象的一生:

@RestController
public class UserController {@GetMapping("/user/{id}")public User getUser(@PathVariable String id) {// 1. id字符串在栈上分配(可能栈分配优化)// 2. User对象在Eden区分配User user = userService.findById(id);// 3. 方法返回,user引用出栈,但User对象仍在堆中// 4. 如果请求频繁,Eden区满,触发Minor GC// 5. 如果user对象仍被外部引用(如缓存),在可达性分析中存活// 6. 经历多次Young GC后,晋升到老年代// 7. 最终缓存失效,对象不可达,被Full GC回收return user;}
}

image

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

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

相关文章

2025 最新桥梁防腐涂料厂家 TOP5推荐!绿色防腐 + 工程实证权威榜单发布,技术赋能守护基建安全 - 全局中转站

随着基础设施建设的持续推进,桥梁作为交通网络的关键节点,其防腐保护需求日益凸显。桥梁防腐涂料不仅需要具备卓越的耐候性、抗腐蚀能力,还需满足环保、安全等多重标准。本榜单基于产品性能、工程案例、环保认证、服…

数据采集实践第四次作业—102302131陈宇新

gitee:https://gitee.com/chenyuxin0328/data-collection/tree/master/作业4 作业1 熟练掌握 Selenium 查找 HTML 元素、爬取 Ajax 网页数据、等待 HTML 元素等内容。 使用 Selenium 框架+ MySQL 数据库存储技术路线爬…

Nginx日志切割

自己部署的个人服务器,主要使用Nginx服务器来做转发和展示很多的静态页面内容,因此每天的日志还是比较多。 日志多了之后,不可能所有的日志都放在同一个文件中,这时候,就需要考虑日志切割,自己是准备按照天保存。…

6502 算术逻辑单元(ALU)

算术逻辑单元(ALU)是 CPU 的核心部件,负责 CPU 内的各种算术运算。现代 CPU 的 ALU 无疑相当复杂,想要从晶体管或逻辑门级别对它的工作原理进行说明几乎不现实。但是 6502 CPU 是一款颇具知名度但又相对简单的 8 位…

make出错立即终止

make出错立即终止Make编译时错误处理与日志输出解决方案 在使用 make -j4 编译 u-Boot(或其他项目)时,实现“错误触发所有线程终止”和“错误日志单独输出”,可通过 make 自身参数 + 脚本辅助实现,以下是两种核心…

Testing Reprised之关于基米

Testing Reprised之关于基米2025.11.10 奇迹就是小有遗憾 你不要有情绪,有情绪就去唱雨中曲(注:今年11月以后我没有什么创作了,就是没有诗和小说,但是会写一些小东西,汇集起来叫做忒思廷大重奏,不知道什么时候有…

Solon AI 开发学习19 - 结合 Solon Flow 实现 ReAct 效果

Solon Flow 是一个基于 YAML/JSON 配置的流程编排引擎,本文演示了其与 solon-ai 结合实现人机交互的 RcAct 流程。该流程通过 LLM 生成文章初稿后进入人工审核循环,支持根据反馈动态修改内容直至审核通过。核心流程包…

网络安全编程——基于Python达成的SSH通信(Windows执行)

网络安全编程——基于Python达成的SSH通信(Windows执行)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

2025 最新水磨石抗污剂厂家 TOP5 评测!环保高性能标杆榜单发布,守护石材持久美观。国内水磨石抗污剂品牌2025年度盘点 - 全局中转站

随着水磨石在商业空间、高端住宅及公共设施中的广泛应用,其易渗污、难养护的问题日益凸显,水磨石抗污剂市场需求持续攀升。本榜单基于产品环保性能、抗污效果持久性、适用场景广泛性及行业口碑四大核心维度,结合国内…

OTOFIX IM2 1-Year Update Subscription: Ensure Latest Vehicle Diagnostics for European/American Cars

Staying Ahead in Automotive Diagnostics: The OTOFIX IM2 One Year Update Service Problem: Outdated Data Holds Back Repairs In today’s rapidly evolving automotive landscape, European and American mechan…

深入解析:2025年11月11日 AI快讯

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

北京守嘉健康干细胞项目介绍 - 品牌排行榜单

守嘉(北京)健康管理有限公司,秉承“引领主动健康,解码生命未来”的理念,通过与生命科学领域的前沿机构北京九思九如健康科技有限公司和北京福安华生物科技有限公司的深度战略合作,整合顶尖科研与临床资源,正式推…

统计文本文件记录

$folderPath = Read-Host "Enter the folder path" function mainMenu { while($prompt -ne ){$totaLines = 0 $n = 0 Get-ChildItem -Path $folderPath -Filter *.txt -Recurse | ForEach-Object { $fileL…

2025最新水洗石抗污剂厂家TOP5评测!环保性能与抗污效果品牌双权威榜单发布,技术赋能重构景观防护生态 - 全局中转站

随着水洗石材料在园林景观、市政工程、商业空间等场景的广泛应用,其抗污防护需求日益凸显。水洗石抗污剂作为提升材料耐久性与美观度的核心产品,市场关注度持续升温。本榜单基于环保安全、抗污性能、场景适配、服务体…

When Ongeki Gets Stuck at the Aime Check

When I updated Ongeki to version 1.21i, I inexplicably ran into the problem of getting stuck at the Aime check. If you are encountering the same issue: I know this is totally messed up, but trust me �…

如果同一个子网中,设备超过255台,那会如何才能保证处于同一子网

要让超过 255 台设备处于同一子网,核心是 扩大网段容量—— 通过选择「网络位更短、主机位更长」的子网掩码(即 CIDR 标识中「/」后面的数字小于 24),让网段可容纳的设备数量超过 253 台(/24 的上限)。关键原理:…

Autel MaxiPRO MP808TS 1-Year Update Subscription: Keep Your Diagnostic Tool Updated Effective

The Challenge: Outdated Diagnostics in a Rapidly Evolving Market Modern vehicles are evolving at an unprecedented pace, with advanced ECUs, hybrid/electric systems, and complex safety protocols becomin…

需求的变更控制

目录1. “变更的影响是可以接受的。”理解方式典型实践2. “受到变更影响的所有人都接到通知并明白这一点。”理解方式典型实践3. “由合适的人选来作出接受变更的正式决定。”理解方式如果没有“正式决定”会发生什么…

在java中实现c#的int.TryParse方法

在java中实现c#的int.TryParse方法在Java中实现类似C#的 int.TryParse方法,确实能带来更安全、更优雅的编码体验(指数字转换)。在.net(c#)的mscorlib程序集中,以System.Int32(int的实际类型)为例,有如下TryParse…

【值得收藏】构建企业级智能体RAG系统:解决大模型五大痛点,让AI真正理解业务 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …