【Java 底层】JVM 垃圾回收机制深度剖析:从对象生死判定到收集器实战

【Java 底层】JVM 垃圾回收机制深度剖析:从对象生死判定到收集器实战

【Java 底层】JVM 垃圾回收机制深度剖析:从对象生死判定到收集器实战

Java 之所以被称为 “开发效率利器”,很大程度上得益于其自动内存管理机制 —— 开发者无需手动分配和释放内存,一切由 JVM 垃圾回收器(Garbage Collector)自动完成。但 “自动” 不代表 “无感知”:内存泄漏、OOM 异常、GC 频繁卡顿等问题,本质上都是对垃圾回收机制理解不深导致的。

本文将从 “如何判断对象该回收”“用什么算法回收”“不同场景选哪种回收器” 三个维度,深入拆解 JVM 垃圾回收的底层逻辑,帮你从根源上解决内存相关问题。

一、前置问题:JVM 为什么需要垃圾回收?

在 C/C++ 中,开发者需要手动调用 malloc() 分配内存、free() 释放内存,一旦遗漏 free() 就会导致内存泄漏(无用内存占用不释放),最终可能引发内存溢出(OOM)。

Java 通过垃圾回收器解决了这个问题:

  • 自动识别 “不再被使用的对象”(垃圾);

  • 自动释放这些对象占用的内存;

  • 优化内存碎片,提高内存利用率。

但垃圾回收并非 “免费午餐”—— 回收过程会暂停应用线程(STW,Stop-The-World),频繁或过长的 STW 会导致应用卡顿。因此,垃圾回收的核心目标是:在尽可能短的 STW 时间内,高效回收无用内存

二、第一步:如何判定对象 “已死”?两种核心算法的博弈

垃圾回收的前提是 “识别垃圾”。JVM 采用两种主流算法判断对象是否存活:引用计数法和可达性分析。

1. 引用计数法:简单但 “有漏洞” 的方案

原理:给每个对象设置一个 “引用计数器”,当有地方引用该对象时,计数器 + 1;引用失效时,计数器 - 1。当计数器为 0 时,认为对象可回收。

优点:实现简单,判断效率高。

缺点:无法解决 “循环引用” 问题。例如:

class A {B b;
}
class B {A a;
}public static void main(String[] args) {A a = new A();B b = new B();a.b = b; // A 引用 Bb.a = a; // B 引用 Aa = null; // 取消外部对 A 的引用b = null; // 取消外部对 B 的引用
}

此时 A 和 B 的引用计数器都是 1(互相引用),但已无外部引用,理应被回收,可引用计数法会误判为 “存活”,导致内存泄漏。

因此,JVM 未采用引用计数法,而是选择了更可靠的可达性分析。

2. 可达性分析法:JVM 的 “标准方案”

原理:以 “GC Roots” 为起点,向下搜索引用链(Reference Chain)。如果一个对象到 GC Roots 没有任何引用链相连(即不可达),则认为该对象可回收。

GC Roots 包含哪些对象?

  • 虚拟机栈中引用的对象(如局部变量、方法参数);

  • 方法区中类静态属性引用的对象(如 static 变量);

  • 方法区中常量引用的对象(如 final 变量);

  • 本地方法栈中 JNI(Native 方法)引用的对象。

示例:上述 A 和 B 的循环引用案例中,a 和 b 被置为 null 后,A 和 B 到 GC Roots 均无引用链,因此会被判定为可回收,解决了循环引用问题。

3. 补充:引用的 “强度分级”

Java 中的 “引用” 并非非黑即白。JDK 1.2 后,引用被分为四级,强度从强到弱依次为:

  • 强引用:普通引用(如 Object obj = new Object()),只要强引用存在,对象永远不会被回收;

  • 软引用SoftReference 包装,内存不足时才会被回收(适合缓存场景);

  • 弱引用WeakReference 包装,下次 GC 时必定被回收(适合临时数据);

  • 虚引用PhantomReference 包装,唯一作用是在对象被回收时收到通知(几乎不用)。

这四种引用让垃圾回收更灵活 —— 例如,缓存数据可用软引用,既保证内存不足时自动释放,又能在有内存时保留缓存。

三、第二步:如何回收?三大核心算法与内存整理

判定对象 “已死” 后,需要回收其占用的内存。JVM 采用三种经典回收算法,各有适用场景。

1. 标记 - 清除算法(Mark-Sweep):最基础但 “有瑕疵”

步骤

  1. 标记:通过可达性分析,标记所有可回收的对象;

  2. 清除:遍历内存,回收所有被标记的对象,释放内存空间。

优点:实现简单,不需要移动对象。

缺点

  • 产生内存碎片:回收后内存中会出现大量不连续的空闲区域,当需要分配大对象时,可能因找不到足够大的连续空间而触发新的 GC;

  • 效率较低:标记和清除过程都需要遍历大量对象。

2. 复制算法(Copying):牺牲空间换效率

步骤

  1. 将内存分为大小相等的两块(From 区和 To 区);

  2. 只使用 From 区,To 区空闲;

  3. GC 时,将 From 区中存活的对象复制到 To 区(按顺序排列,无碎片);

  4. 清空 From 区,交换 From 和 To 区的角色,重复使用。

优点

  • 无内存碎片;

  • 回收效率高(只需复制存活对象,存活对象少的时候效率极高)。

缺点

  • 内存利用率低(仅能使用一半内存);

  • 不适合存活对象多的场景(如老年代,复制成本太高)。

应用:JVM 年轻代(Eden 区和 Survivor 区)主要采用复制算法。年轻代中对象存活率低,复制成本小,且通过 “Eden + 2 个 Survivor 区(8:1:1)” 的设计,将内存浪费控制在 10%(只留 1 个 Survivor 区空闲)。

3. 标记 - 整理算法(Mark-Compact):老年代的 “专属方案”

步骤

  1. 标记:同标记 - 清除算法,标记可回收对象;

  2. 整理:将所有存活对象向内存一端移动,然后直接清理掉边界外的内存。

优点

  • 无内存碎片;

  • 内存利用率 100%(无需牺牲一半空间)。

缺点

  • 增加了 “移动对象” 的成本,效率比复制算法低。

应用:JVM 老年代主要采用标记 - 整理算法。老年代中对象存活率高,移动成本虽高,但避免了内存碎片和空间浪费,是更平衡的选择。

四、第三步:谁来执行回收?经典垃圾收集器的 “看家本领”

垃圾收集器是算法的具体实现。JVM 提供了多种收集器,各有侧重,需根据应用场景选择。

1. Serial GC:单线程回收,简单高效但卡顿明显

特点

  • 单线程执行 GC,GC 时暂停所有应用线程(STW);

  • 采用 “复制算法(年轻代)+ 标记 - 整理算法(老年代)”;

  • 实现简单,内存占用少,适合单核 CPU 或小内存应用(如嵌入式设备)。

缺点:STW 时间长(尤其老年代回收时),不适合大内存、高并发场景。

2. Parallel GC:多线程 “吞吐量优先”

特点

  • 多线程执行 GC(年轻代和老年代均用多线程),缩短 STW 时间;

  • 目标是 “高吞吐量”(吞吐量 = 运行用户代码时间 / 总时间);

  • 可通过参数控制吞吐量(如 -XX:MaxGCPauseMillis 限制最大 STW 时间,-XX:GCTimeRatio 控制 GC 时间占比)。

应用:适合后台任务、批处理程序等对吞吐量敏感,对延迟要求不高的场景。

3. CMS GC:“低延迟” 的并发回收器(已逐渐被淘汰)

CMS(Concurrent Mark Sweep) 是第一款真正意义上的并发收集器,目标是 “最短 STW 时间”。

步骤(分四阶段)

  1. 初始标记(STW):快速标记 GC Roots 直接关联的对象(耗时短);

  2. 并发标记:GC 线程与应用线程并发执行,遍历引用链,标记所有可达对象(耗时最长,但不阻塞应用);

  3. 重新标记(STW):修正并发标记期间因应用线程运行导致的标记变动(耗时较短);

  4. 并发清除:GC 线程与应用线程并发执行,回收被标记的对象(不阻塞应用)。

优点:STW 时间短,适合对延迟敏感的应用(如 Web 服务)。

缺点

  • 并发阶段占用 CPU 资源,可能导致应用响应变慢;

  • 采用标记 - 清除算法,会产生内存碎片;

  • 对大内存支持不好(并发标记时内存占用高);

  • JDK 9 中被标记为 deprecated,JDK 14 中移除。

4. G1 GC:面向大内存的 “区域化” 收集器

G1(Garbage-First)是为替代 CMS 设计的,适合 4GB 以上大内存场景,兼顾吞吐量和延迟。

核心创新

  • 区域化内存布局:将堆内存划分为多个大小相等的独立区域(Region),每个区域可动态扮演年轻代或老年代,灵活分配内存;

  • Mixed GC:优先回收 “垃圾最多的区域”(Garbage-First),提高回收效率;

  • 停顿预测模型:根据历史数据预测 STW 时间,确保不超过用户设置的目标(如 -XX:MaxGCPauseMillis=200)。

步骤:类似 CMS,但增加了 “筛选回收” 阶段,只回收垃圾多的区域,减少 STW 时间。

优点

  • 大内存下表现优异,STW 时间可控;

  • 无内存碎片(区域内采用复制算法,整体类似标记 - 整理);

  • 兼顾吞吐量和延迟,是当前主流收集器之一。

5. ZGC/Shenandoah:超低延迟的新一代收集器

JDK 11 引入 ZGC,JDK 12 引入 Shenandoah,二者均为 “低延迟、高并发” 收集器,STW 时间可控制在毫秒级甚至微秒级,适合超大内存(TB 级)场景。

核心技术

  • 着色指针:通过指针标记对象状态(如是否被标记、是否可回收),避免传统的 “标记 - 清除” 流程;

  • 读屏障 / 写屏障:在对象引用读写时插入少量代码,实现并发标记和移动,几乎不阻塞应用线程。

应用:对延迟要求极高的场景(如高频交易、实时数据分析),但目前在生产环境中的应用不如 G1 广泛。

五、实战避坑:GC 问题的排查与优化思路
1. 常见问题及原因:
  • 频繁 Full GC:可能是内存泄漏(对象长期被引用无法回收)、老年代对象增长过快(如大对象直接进入老年代);

  • STW 时间过长:收集器选择不当(如用 Serial GC 处理大内存)、堆内存设置不合理(太大或太小);

  • OOM 异常:堆内存不足(需调大 -Xmx)、永久代 / 元空间不足(调大 -XX:MaxMetaspaceSize)。

2. 优化原则:
  • 根据场景选收集器:延迟敏感用 G1/ZGC,吞吐量优先用 Parallel GC;

  • 合理设置堆内存:避免太小(GC 频繁)或太大(单次 GC 时间长),通常建议为物理内存的 1/2 ~ 1/4;

  • 减少大对象:大对象直接进入老年代,易触发 Full GC,尽量拆分或复用对象;

  • 监控与调优:通过 jstatjconsole 或可视化工具(如 GCEasy)监控 GC 频率和 STW 时间,逐步调整参数。

六、总结:垃圾回收的 “本质” 是平衡的艺术

JVM 垃圾回收的核心是 “在效率、延迟、内存利用率之间找平衡”:

  • 年轻代用复制算法,优先效率;

  • 老年代用标记 - 整理算法,优先避免碎片;

  • 收集器选择则根据 “吞吐量” 或 “延迟” 需求,从 Serial 到 ZGC,本质是对 “并发” 和 “STW” 的权衡。

理解这些底层逻辑,不仅能解决 GC 相关问题,更能帮你写出更 “内存友好” 的代码 —— 比如避免创建不必要的对象、及时释放无用引用、合理设计缓存策略等。毕竟,最好的 GC 优化,是从代码层面减少垃圾的产生。

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

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

相关文章

网络问题排查

网络连通性测试:ping ip持续性监测:ping -t ipnetstat 可以查看网络连接状态,可以看到显示系统的网络连接,路由表,接口等信息。netstat -nult 回车-t:显示的是tcp的连接-u:显示udp的连接-l:只显示监听状态的端口-n:显示…

tuple/dict/list 这三个数据类型在取值时候的区别

tuple(元组)、dict(字典)、list(列表)在取值时的区别。 1. list(列表) 👉 列表就是“一串有顺序的东西”,像排队的人。 取值方式:用 下标&#xf…

深度解析大模型服务性能评测:AI Ping平台助力开发者精准选型MaaS服务

深度解析大模型服务性能评测:AI Ping平台助力开发者精准选型MaaS服务 🌟 Hello,我是摘星! 🌈 在彩虹般绚烂的技术栈中,我是那个永不停歇的色彩收集者。 🦋 每一个优化都是我培育的花朵&#xff…

OpenCV物体跟踪:从理论到实战的全面解析

​ 一、引言​ 在计算机视觉的广阔领域中,物体跟踪技术宛如一颗璀璨的明星,散发着独特的魅力与价值,发挥着举足轻重的作用。它致力于在连续的图像帧或视频序列里,精准识别并持续定位特定的目标物体,这一过程看似简单…

【Python】OS模块操作目录

1、概述os模块是一个Python内置的操作目录和查看系统基础信息的模块,可用于让我们对目录进行批量操作,其中包括:查看系统信息(环境变量、分隔符、换行符等),对目录进行创建、删除、重命名、查看目录内容等&…

JavaScript中 i++ 与 ++i

在 JavaScript 编程中,i(前置自增)和i(后置自增)是两个常用但极易混淆的运算符。它们看似都能实现变量自增 1 的功能,但其执行时机和返回值的差异,常常导致开发者在实际编码中出现逻辑错误。本文…

fastapi 中间件的使用

1. 中间件基础结构from starlette.middleware.base import BaseHTTPMiddlewareclass RequestLoggerMiddleware(BaseHTTPMiddleware):async def dispatch(self, request: Request, call_next):# 1. 请求处理前逻辑# 2. 调用后续处理response await call_next(request)# 3. 响应…

网络白菜包子手动安装 Arch Linux

大家好!我是大聪明-PLUS!针对初学者的 Arch Linux 安装详细教程。我曾经花了不少时间才搞清楚安装过程。本文旨在提供一种“捷径”,让每个人都能轻松上手,无论他们是否有 Linux 使用经验。Arch 的主要特点是极其灵活的系统配置&am…

Linux学习笔记(五)--Linux基础开发工具使用

在Linux中软件包通常是指一个包含了软件程序、元数据、依赖关系信息和安装脚本的压缩文件​​。因为在Linux上如果没有软件包管理器,那么想要下载软件会非常麻烦,不仅需要自己去手动编译和安装,而且难以卸载和管理,所以软件包的出现解决了这些问题.软件包一般是由程序文件(编译…

数据结构(陈越,何钦铭) 第十讲 排序(下)

10.1 快速排序 10.1.1 算法概述10.1.2 选主元10.1.3 子集划分10.1.4 算法实现10.2 表排序 10.2.1 算法概述10.2.2 物理排序10.3 基数排序 10.3.1 桶排序10.3.2 基数排序10.3.3 多关键字的排序10.4 排序算法的比较

vue 使用print.js 打印文本,HTML元素,图片,PDF

vue 使用print.js 打印文本,HTML元素,图片,PDF 安装 npm install print-js --save示例 <template><div class"print-example"><h2>Print.js 打印示例</h2><!-- 打印区域 --><div id"printableArea" class"printable…

jenkins审批机器人功能概述-Telegram版

Jenkins审批机器人 - 功能概述 代码链接&#xff0c;私聊可得 项目简介 Jenkins审批机器人是一个集成Jenkins CI/CD流程的自动化审批系统&#xff0c;通过Telegram机器人提供便捷的发布审批功能。该系统支持多环境部署审批、用户权限管理、构建结果通知等完整的DevOps审批流程。…

Rust : 关于解引用“*”

关于解引用*操作符&#xff0c;谨供参考&#xff01; 一、主要代码 use std::ops::Deref; fn main() {model_1();model_2();model_3();model_4();model_5();model_6();model_7();model_8();model_9(); }二、*操作符与常见的引用和解引用 fn model_1(){let reference:&St…

【高级终端Termux】在安卓手机/平板上使用Termux 搭建 Debian 环境并运行 PC 级 Linux 应用教程(含安装WPS,VS Code)

Termux 搭建 Debian 环境并运行 PC 级 Linux 应用教程 一、前言 1. 背景 众所周知&#xff0c;最新搭载澎湃OS和鸿蒙OS的平板都内置了PC级WPS&#xff0c;办公效率直接拉满&#xff08;板子终于从“泡面盖”升级为“生产力”了&#xff09;。但问题来了&#xff1a;如果不是这…

从循环到矩阵运算:矢量化加速机器学习的秘诀

矢量化实现全解析&#xff1a;从原理到实战 在学习数据科学、机器学习和深度学习的过程中&#xff0c;我们经常会听到一个高频词——矢量化&#xff08;Vectorization&#xff09;。很多库的官方文档、教程里也不断强调“要尽量使用矢量化操作&#xff0c;而不是显式循环”。那…

大数据毕业设计-大数据-基于大数据的热门游戏推荐与可视化系统(高分计算机毕业设计选题·定制开发·真正大数据)

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

从零到一:用 Qt + libmodbus 做一个**靠谱**的 Modbus RTU 小工具(实战总结)

文章目录从零到一&#xff1a;用 Qt libmodbus 做一个**靠谱**的 Modbus RTU 小工具&#xff08;实战总结&#xff09;你会得到什么快速背景&#xff1a;为什么是 Modbus RTU&#xff1f;协议速查&#xff08;够用不啰嗦&#xff09;工程结构与 UI 组织连接“三板斧”&#xf…

使用Python创建本地Http服务实现与外部系统数据对接

在Python 3.10中创建一个能够处理GET和POST请求的本地HTTP服务器&#xff0c;并提供一个默认的 index.html 页面是完全可行的。Python的标准库中的 http.server 模块虽然简单&#xff0c;但通过一些自定义扩展可以满足这个需求。 下面我将提供一个实现方案&#xff0c;它包含一…

了解篇 | StarRocks 是个什么数据库?

今天简要介绍一下StarRocks 这个强大的数据库。注意&#xff1a;本文章内容仅供个人学习&#xff0c;不包含任何推荐性质。StarRocks&#xff08;原名 Doris&#xff09;是一个高性能、全场景的MPP&#xff08;大规模并行处理&#xff09;分析型数据库。它专为极速的多维联机分…

SSL部署完成,https显示连接不安全如何处理?

在部署 SSL 后&#xff0c;如果浏览器仍然显示 “连接不安全” 或 “Not Secure”&#xff0c;通常是由以下几种原因导致的。针对每种可能的原因和问题&#xff0c;以下提供了详细的排查和解决方案。 1. 排查问题的可能原因 1.1 SSL 证书未正确安装 如果 SSL 证书安装不完整或…