一篇文章带你彻底搞懂 JVM 垃圾收集器

垃圾收集器是 JVM 内存管理的执行引擎,负责自动回收无用的对象内存。其设计核心是 权衡:主要是吞吐量停顿时间之间的权衡。没有“最好”的收集器,只有“最适合”特定场景的收集器。

一、核心基础:分代收集模型

主流 HotSpot JVM 采用分代模型,将堆内存划分为不同区域,基于对象存活周期的“弱分代假说”采用不同的收集策略:

新生代

  • 存放新创建的对象。特点是区域小、对象存活率低、回收频繁。

    • Eden区:对象诞生的地方。

    • Survivor幸存者区 :存放 Minor GC 后存活的对象。

老年代

  • 存放经过多次 Young GC 后依然存活的对象。特点是区域大、对象存活率高。

  • 元空间 :存放类元数据等,由本地内存提供,不属堆内存。

垃圾收集类型:

  • Minor GC / Young GC:只收集年轻代的垃圾。

  • Major GC / Old GC:只收集老年代的垃圾。

  • Full GC:收集整个堆(新生代 + 老年代 + 方法区)的垃圾。

二、垃圾收集算法

分代收集理论:

目前主流JVM虚拟机中的垃圾收集器,都遵循分代收集理论:

弱分代:绝大多数对象都是朝生夕灭
强分代:经历越多次垃圾收集过程的对象,越难以回收,难以消亡

按照分代收集理论设计的“分代垃圾收集器”,所采用的设计原则:收集器应该将Java堆划分成不同的区域,然后将回收对象依据其年龄(年龄即对象经历过垃圾收集过程的次数)分配到不同的区域存储。

分代存储:

  • 如果一个区域中大多数对象都是朝生夕灭(新生代),难以熬过垃圾收集过程的话,把它们集中存储在一起,每次回收时,只关注如何保留少量存活对象,而不是去标记大量将要回收的对象,就能以较低代价回收到大量的空间。
  • 如果一个区域中大多数对象都是难以回收(老年代),那么把它们集中放在一起,JVM虚拟机就可以使用较低的频率,来对这个区域进行回收。

这样设计的好处是,兼顾垃圾收集的时间开销和内存空间的有效利用。

分代收集:

堆区按照分代存储的好处:
  • 在Java堆区划分成不同区域后,垃圾收集器才可以每次只回收其中某一个或者某些区域,所以才有MinorGC、MajorGC、FullGC等垃圾收集类型划分。
  • 在Java堆区划分成不同区域后,垃圾收集器才可以针对不同的区域,安排与该区域存储对象存亡特征相匹配的垃圾收集算法:标记-复制算法、标记-清除算法、标记-整理算法等。

垃圾收集算法:

垃圾收集算法主要解决三个问题:

  1. 如何判断对象已死?(标记阶段)

  2. 如何回收垃圾对象占用的内存?(清除阶段)

  3. 如何避免内存碎片?(整理阶段)

围绕这些问题,衍生出了以下几种基础算法。

1. 基础:如何判断对象“已死”?

垃圾收集的前提是判断对象是否存活。主要有两种算法:

(1). 引用计数算法
  • 机制:在对象中添加一个引用计数器。每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一。任何时刻计数器为零的对象就是不可能再被使用的。

  • 优点:原理简单,判定效率高。

  • 缺点无法解决对象之间循环引用的问题(对象A引用B,对象B引用A,但再无第三方引用它们俩)。因此,主流Java虚拟机均不采用此算法

(2). 可达性分析算法
  • 机制:通过一系列称为 “GC Roots” 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为 “引用链”。如果某个对象到GC Roots间没有任何引用链相连(即从GC Roots到这个对象不可达),则证明此对象是不可能再被使用的。

  • Java中可作为GC Roots的对象包括

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。

    • 方法区中类静态属性引用的对象。

    • 方法区中常量引用的对象。

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

    • Java虚拟机内部的引用(如基本数据类型对应的Class对象,常驻的异常对象等)。

    • 所有被同步锁(synchronized关键字)持有的对象。

  • 这是目前主流Java虚拟机采用的判断对象是否存活的算法。

2. 核心:垃圾收集算法

确定了哪些对象是垃圾之后,就需要进行回收。主要有三种基础算法:

1. 标记-清除算法
  • 过程:算法分为“标记”和“清除”两个阶段。

    1. 标记:首先通过可达性分析,标记出所有需要回收的对象。

    2. 清除:随后,统一回收掉所有被标记的对象。

  • 优点:是最基础的收集算法,后续算法都是在其基础上改进的。

  • 缺点

    • 执行效率不稳定:如果堆中包含大量需要回收的对象,则标记和清除两个过程的执行效率都会随之降低。

    • 内存碎片化:标记、清除之后会产生大量不连续的内存碎片。碎片太多可能导致以后在分配大对象时无法找到足够的连续内存,从而不得不提前触发另一次垃圾收集。

2. 复制算法
  • 过程:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

  • 优点

    • 高效:只需要按顺序分配内存,移动堆顶指针即可,分配新对象的速度极快。

    • 无碎片:复制过程中会将存活对象紧凑地排列在另一块内存,完全避免了碎片问题。

  • 缺点内存利用率低,可用内存缩小为了原来的一半。

  • 应用是HotSpot虚拟机中年轻代垃圾收集的核心算法。但商业虚拟机都对其进行了优化:并不按1:1的比例划分内存,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间(通常是8:1:1)。每次分配只使用Eden和其中一块Survivor。发生Minor GC时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。这样只有10%的内存会被“浪费”。

3. 标记-整理算法
  • 过程:也分为“标记”和“整理”两个阶段。

    1. 标记:与“标记-清除”算法一样,首先标记所有需要回收的对象。

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

  • 优点

    • 无内存碎片:整理后内存是紧凑的。

    • 内存利用率高:无需浪费一半的内存空间。

  • 缺点“整理”阶段涉及大量对象的移动,并且需要更新所有引用这些对象的指针,是一种开销较大的操作,而且移动对象时必须全程暂停用户应用程序(Stop The World)。

  • 应用主要用于老年代的垃圾收集,如Serial Old和Parallel Old收集器。

4. 分代收集算法
  • 本质:这并非一种新的算法思想,而是上述三种算法的实际应用范式

  • 思路:根据对象存活周期的不同,将Java堆划分为年轻代老年代

    • 在年轻代:对象“朝生夕死”,存活率低,回收频繁。因此选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集,且速度最快。

    • 在老年代:对象存活率高,没有额外的空间对它进行分配担保。因此必须使用 “标记-清除”或“标记-整理” 算法来进行回收。

3. 垃圾收集算法总结:

算法过程优点缺点适用场景
标记-清除标记 → 清除实现简单效率低、产生碎片老年代 (CMS)
复制标记 → 复制到另一半 → 清空原空间效率高、无碎片浪费一半空间年轻代 (几乎所有收集器)
标记-整理标记 → 整理存活对象到一端 → 清理边界无碎片、空间利用率高移动对象成本高老年代 (Serial Old, Parallel Old)
分代收集根据代的特点选用上述算法综合优势,扬长避短实现复杂现代JVM的实际实践

三、经典垃圾收集器详解

垃圾收集器是内存回收算法的具体实现。不同收集器适用于不同场景,核心目标是在 吞吐量 和 停顿时间 之间取得最佳平衡。

1. Serial 收集器(新生代)

  • 特点单线程工作的收集器。在进行垃圾收集时,必须暂停所有用户工作线程("Stop The World"),直到收集结束。

  • 算法:复制算法。

  • 定位:是 Client 模式下的默认新生代收集器。简单而高效,没有线程交互开销。适用于内存资源受限的客户端应用或单核服务器环境。

2. Serial Old 收集器(老年代)

  • 特点Serial 收集器的老年代版本,同样是一个单线程收集器。

  • 算法:标记-整理算法。

  • 定位:主要用于 Client 模式。在 Server 模式下,它主要作为 CMS 收集器失败时的后备预案(Concurrent Mode Failure)。

3. ParNew 收集器(新生代)

  • 特点:本质上是 Serial 收集器的多线程并行版本。它使用多条线程进行垃圾收集,但在收集时同样需要“Stop The World”。

  • 算法:复制算法。

  • 定位:在 JDK 7 之前,它是许多服务端应用的首选新生代收集器,因为它是 唯一能与 CMS 收集器配合工作 的新生代收集器。

4. Parallel Scavenge 收集器(新生代)

  • 特点:也称为“吞吐量优先”收集器。它使用并行多线程进行收集,但其关注点是达到一个 可控制的吞吐量(吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。

  • 算法:复制算法。

  • 定位:适合后台运算、科学计算等 不需要低延迟,但需要高吞吐量 的任务。是 JDK 8 默认的新生代收集器

5. Parallel Old 收集器(老年代)

  • 特点Parallel Scavenge 收集器的老年代版本,使用多线程进行收集。

  • 算法:标记-整理算法。

  • 定位:在 JDK 6 之后才开始提供。它的出现使得 Parallel Scavenge + Parallel Old 成为一套真正专注于 高吞吐量 的完整组合。

6. CMS 收集器(老年代)

  • 目标:以 获取最短回收停顿时间 为目标,注重用户体验。

  • 过程:其核心过程有四步骤:

    1. 初始标记 (Stop The World):速度极快。

    2. 并发标记 (与用户线程并发):耗时最长但无需停顿。

    3. 重新标记 (Stop The World):修正并发标记期间的变化。

    4. 并发清除 (与用户线程并发)。

  • 算法:标记-清除算法(会产生碎片)。

  • 缺点:对CPU资源敏感、无法处理“浮动垃圾”、会产生内存碎片。

  • 定位:适用于互联网站、B/S系统的服务端,重视服务的响应速度。现已不推荐使用,被 G1 等收集器取代。

7. G1 收集器(老年代)

  • 革新:放弃了传统物理连续的分代模型,将堆划分为多个大小相等的独立区域(Region)。虽然保留分代概念,但年轻代和老年代是这些 Region 的动态集合。

  • 目标可预测的停顿时间模型。允许用户指定期望的停顿时间。

  • 工作机制:跟踪各个 Region 的回收价值(空间大小及回收成本),优先回收价值最大的 Region。

  • 算法:整体上看是标记-整理算法,局部(两个Region之间)基于复制算法。

在不同的应用场景下,选择合适的垃圾收集器至关重要。对于追求高吞吐量的后台计算任务,Parallel Scavenge与Parallel Old的组合是最佳选择;若需要低延迟的响应式服务,CMS或新一代的G1收集器更为合适;而对于内存敏感的单核环境,Serial收集器仍是最实用的选项。随着技术发展,G1收集器凭借其平衡的吞吐量和延迟表现,已成为大多数现代应用的默认推荐。在选择时,还需考虑JDK版本和具体的性能需求,才能做出最合适的决策。

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

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

相关文章

服务器排故随笔:服务器无法ssh远程登录

文章目录服务器排故随笔:服务器无法远程登录问题现象解决过程第一步:确认故障描述是否准确第二步:确认网络是否有问题第三步:确认ssh服务是否有问题第四步:确认防火墙是否放行sshd服务第五步:试试万能的“重…

Deeplizard深度学习课程(六)—— 结合Tensorboard进行结果分析

前言 Tensorboard最初是tensorflow的可视化工具,被用于机器学习实验的可视化,后来也适配了pytorch。Tensorboard是一个前端web界面,,能够从文件里面读取数据并展示它(比如损失、准确率、网络图)。具体使用可…

C语言————实战项目“扫雷游戏”(完整代码)

无论是找工作面试,还是课设大作业、考研,都离不开实战项目的积累,如果你能把一个项目搞明白,并且给别人熟练的讲出来,即使你没有过项目经历,也可以说是非常加分的,下面来沉浸式体验一下这款扫雷…

数据结构之加餐篇 -顺序表和链表加餐

目录一、链表分割二、随机链表的复制总结一、链表分割 链表分割 题目描述的意思就如下图: 也就是把1,2挪到前面,6,3,5挪到后面,前者的相对顺序不发生改变 这里要想往后挪就要先遍历,遍历到6…

JSP与Servlet整合数据库开发:构建Java Web应用的全栈指南

JSP与Servlet整合数据库开发:构建Java Web应用的全栈指南 概述 在Java Web开发领域,JSP(JavaServer Pages)与Servlet是构建动态Web应用的核心技术组合。Servlet作为Java EE的基础组件,负责处理客户端请求、执行业务逻…

设计五种算法精确的身份证号匹配

问题定义与数据准备 我们有两个Excel文件: small.xlsx: 包含约5,000条记录。large.xlsx: 包含约140,000条记录。 目标:快速、高效地从large.xlsx中找出所有其“身份证号”字段存在于small.xlsx“身份证号”字段中的记录,并将这些匹配的记录保…

Spring 框架(IoC、AOP、Spring Boot) 的必会知识点汇总

目录:🧠 一、Spring 框架概述1. Spring 的核心功能2. Spring 模块化结构🧩 二、IoC(控制反转)核心知识点1. IoC 的核心思想2. Bean 的定义与管理3. IoC 容器的核心接口4. Spring Bean 的创建方式🧱 三、AOP…

简单工厂模式(Simple Factory Pattern)​​ 详解

✅作者简介:大家好,我是 Meteors., 向往着更加简洁高效的代码写法与编程方式,持续分享Java技术内容。 🍎个人主页: Meteors.的博客 💞当前专栏: 设计模式 ✨特色专栏: 知识分享 &…

新电脑硬盘如何分区?3个必知技巧避免“空间浪费症”!

刚到手的新电脑,硬盘就像一间空荡荡的大仓库,文件扔进去没多久就乱成一锅粥?别急,本文会告诉你新电脑硬盘如何分区,这些方法不仅可以帮你给硬盘分区,还可以调整/合并分区大小等。所以,本文的分区…

【微知】git submodule的一些用法总结(不断更新)

文章目录综述要点细节如何新增一个submodule?如何手动.gitmodules修改首次增加一个submodule?git submodule init,init子命令依据.gitmodules.gitmodules如何命令修改某个成员以及同步?如果submodule需要修改分支怎么办&#xff1…

【Spring Cloud微服务】9.一站式掌握 Seata:架构设计与 AT、TCC、Saga、XA 模式选型指南

文章目录一、Seata 框架概述二、核心功能特性三、整体架构与三大角色1. Transaction Coordinator (TC) - 事务协调器(Seata Server)2. Transaction Manager (TM) - 事务管理器(集成在客户端)3. Resource Manager (RM) - 资源管理器…

AI赋能!Playwright带飞UI自动化脚本维护

80%的自动化脚本因一次改版报废? 开发随意改动ID导致脚本集体崩溃?背景UI自动化在敏捷开发席卷行业的今天,UI自动化测试深陷一个尴尬困局:需求迭代速度(平均2周1次)> 脚本维护速度(平…

Redis、Zookeeper 与关系型数据库分布式锁方案对比及性能优化实战指南

Redis、Zookeeper 与关系型数据库分布式锁方案对比及性能优化实战指南 1. 问题背景介绍 在分布式系统中,多节点并发访问共享资源时,如果不加锁或加锁不当,会导致数据不一致、超卖超买、竞态条件等问题。常见的分布式锁方案包括基于Redis、Zoo…

网络安全A模块专项练习任务十一解析

任务十一:IP安全协议配置任务环境说明: (Windows 2008)系统:用户名Administrator,密码Pssw0rd1.指定触发SYN洪水攻击保护所必须超过的TCP连接请求数阈值为5;使用组合键winR,输入regedit打开注册表编辑器&am…

金蝶中间件适配HGDB

文章目录环境文档用途详细信息环境 系统平台:Microsoft Windows (64-bit) 10 版本:5.6.5 文档用途 本文章主要介绍金蝶中间件简单适配HGDB。 详细信息 一、金蝶中间件Apusic安装与配置 1.Apusic安装与配置 Windows和Linux下安装部署过程相同。 &…

使用a标签跳转之后,会刷新一次,这个a标签添加的样式就会消失

<ul class"header-link"><li><a href"storeActive.html">到店活动</a></li><li><a href"fuwu.html">服务</a></li><li><a href"store.html">门店</a></l…

线程池实现及参数详解

线程池概述 Java线程池是一种池化技术&#xff0c;用于管理和复用线程&#xff0c;减少线程创建和销毁的开销&#xff0c;提高系统性能。Java通过java.util.concurrent包提供了强大的线程池支持。 线程池参数详解 1. 核心参数 // 创建线程池的完整构造函数 ThreadPoolExecu…

K8S 部署 NFS Dynamic Provisioning(动态存储供应)

K8S 部署 NFS Dynamic Provisioning&#xff08;动态存储供应&#xff09; 本文档提供完整的 K8s NFS 动态存储部署流程&#xff0c;包含命名空间创建、RBAC 权限配置、Provisioner 部署、StorageClass 创建及验证步骤。 2. 部署步骤 2.1 创建命名空间 首先创建独立的命名空间 …

JavaEE 进阶第二期:开启前端入门之旅(二)

专栏&#xff1a;JavaEE 进阶跃迁营 个人主页&#xff1a;手握风云 目录 一、VS Code开发工具的搭建 1.1. 创建.html文件 1.2. 安装插件 1.3. 快速生成代码 二、HTML常见标签 2.1. 换行标签 2.2. 图片标签: img 2.3. 超链接 三、表格标签 四、表单标签 4.1. input标…

【RNN-LSTM-GRU】第二篇 序列模型原理深度剖析:从RNN到LSTM与GRU

本文将深入探讨循环神经网络&#xff08;RNN&#xff09;的核心原理、其面临的长期依赖问题&#xff0c;以及两大革命性解决方案——LSTM和GRU的门控机制&#xff0c;并通过实例和代码帮助读者彻底理解其工作细节。1. 引言&#xff1a;时序建模的数学本质在上一篇概述中&#x…