Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >JVM 八股之首:三大垃圾收集算法

JVM 八股之首:三大垃圾收集算法

作者头像
飞天小牛肉
发布于 2022-05-24 07:16:48
发布于 2022-05-24 07:16:48
7270
举报
文章被收录于专栏:飞天小牛肉飞天小牛肉

前文介绍过,基于分代收集理论的指导,我们才可以针对堆中不同的区域,设计出不同的垃圾收集算法,主要有以下三种:

  • 标记-清除算法
  • 标记-复制算法
  • 标记-整理算法

全文思维导图如下:

老规矩,背诵版在文末

标记-清除算法,Mark-Sweep

“标记-清除”(Mark-Sweep)算法是最基础的垃圾收集算法,在 1960 年由 Lisp 之父 John McCarthy 所提出,后面所介绍的两种算法都是基于此改进而来

不难理解,从名称上就已经能看出,整个算法分为两个大步骤:

标记 and 清除。

拓展来说:

  • 标记,Mark:指的是标记出所有需要回收的对象(也就是判断对象是否是垃圾,这个前文已经说过了,有两种方法:引用计数法和可达性分析,由于引用计数法无法解决循环引用问题,所以目前主流的虚拟机采用的都是可达性分析法)
  • 清除,Sweep:指的是在标记完成后,统一回收掉所有被标记的对象

当然了,反过来也是可以的,标记存活的对象,统一回收所有未被标记的对象。

我们说这个标记-清除算法是最基础的垃圾收集算法奥,后面两种算法都是基于此改进而来,那么改进改进,既然是改进,这个基础的算法一定是存在一些问题,才能够有改进的空间,对吧。

标记-清除算法的主要缺点有两个:

  • First:执行效率不稳定。如果堆中包含大量对象,而且其中大部分是需要被回收的,这时就必须进行大量的标记和清除的动作,导致标记和清除两个过程的执行效率都辉随着对象数量的增长而降低,也就是执行效率和对象数量成反比。
  • Second:内存空间的碎片化问题。标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当程序运行过程中需要分配较大对象时,因无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

标记-清除算法的执行过程如图:

标记-复制算法,Mark-Copy

“标记-复制”(Mark-Copy)算法常被简称为复制算法。

为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,1969 年 Fenichel 提出了一种称为 “半区复制”(Semispace Copying)的垃圾收集算法,具体思想大概是这样:

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

很显然,这个方法并不适用于多数对象都是存活的情况,因为这将会产生大量的内存间复制的开销。

但对于多数对象都是可回收的情况,该算法只需要复制少量的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。

这样实现简单,运行高效,现在大部分的商用 Java 虚拟机都优先采用了这种垃圾收集算法去回收新生代

该算法的执行过程如图所示:

这样实现简单,运行高效,不过其缺陷也显而易见,这种复制回收算法的代价是将可用内存缩小为了原来的一半,空间浪费未免太多了一点。

IBM 公司曾有一项专门研究对新生代 “朝生夕灭” 的特点做了更量化的诠释:新生代中的对象有 98% 熬不过第一轮收集。因此并不需要按照 1∶1的比例来划分新生代的内存空间

更简单的来说,标记-复制算法设计这么一大块的保留区域,目的就是为了把存活对象移动到这块区域上来,方便对之前的区域进行快速清理。

对于新生代对象来说,其具备的鲜明特点就是 “朝生夕灭”,能够在一轮垃圾收集后活下来的对象少之又少。所以,我们其实并不需要这么大一块的保留区域。

1989 年 Andrew Appel 基于此提出了一种更优化的半区复制分代策略,现在称为 “Appel 式回收”。HotSpot 虚拟机的 Serial、ParNew 等新生代收集器均采用了这种策略来设计新生代的内存布局。

⭐ Appel 式回收的具体做法是把新生代分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次分配内存只使用 Eden 和其中一块 Survivor。发生垃圾收集时,直接清空 Eden 和已用过的那块 Survivor 空间,当然,在清空之前需要将存活对象复制到另一块 Survivor 中。

这两块 Survivor 空间也分别被称为 From Survivior 和 To Survivor,很显然,每经过一次新生代 GC,From Survivor 和 To Survivor 的身份就会互换。

简单理解,Eden 和 From Survivor 其实就是新生代能够使用的真正内存,而 To Survivor 的存在是为了在清空新生代空间时提供一个地方用来存放仍然存活的对象 (也即保留区域)

HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8∶1,也即每次新生代中可用内存空间为整个新生代容量的 90%(Eden 的 80% 加上一个 Survivor 的 10%),只有一个 Survivor 空间,即 10% 的新生代空间是会被 “浪费” 的。

当然,任何人都没有办法百分百保证每次回收都只有不多于 10% 的对象存活,万一 To Survivor 的内存空间不足以容纳存活的对象怎么办?

别急,我们都能想到,祖宗能想不到?

Appel 式回收还有一个充当罕见情况的 “逃生门” 的安全设计:当 To Survivor 空间不足以容纳一次新生代 GC 之后存活的对象时,这些对象便将通过分配担保机制(Handle Promotion)直接进入老年代。

所谓分配担保,后续文章介绍垃圾收集器的时候会再详细解释

标记-整理算法,Mark-Compact

Mark-Copy 算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费 50% 的空间,使用 Apple 式回收的话,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都 100% 存活的极端情况,所以在老年代一般不能直接选用 Mark-Copy 算法

针对老年代对象的存亡特征,1974 年 Edward Lueders 提出了另外一种有针对性的 “标记-整理”(Mark-Compact)算法

其中的标记过程还是一样的,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存,如图所示:

Mark-Sweep 算法与 Mark-Compact 算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策

  • 如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行,像这样的停顿被最初的虚拟机设计者形象地描述为 “Stop The World (STW)”。(记住这个名词 STW,后续我们会经常见到他!!!移动存活对象时需要 STW,可达性分析中的根节点枚举也需要 STW) 总结来说:移动则内存回收时会更复杂
  • 如果完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决,而内存的访问是用户程序最频繁的操作,甚至都没有之一,假如在这个环节上增加了额外的负担,势必会直接影响应用程序的吞吐量。 总结来说:不移动则内存分配时会更复杂

⭐ 从垃圾收集的停顿时间来看,不移动对象的停顿时间会更短,甚至可以不需要停顿,但是从整个程序的吞吐量来看,移动对象会更划算。

这里的吞吐量,简单理解,就是用户程序和垃圾收集器的效率总和

所以我们其实可以推断出:

  • 关注延迟/速度的收集器(比如 HotSpot 虚拟机中的 CMS 收集器)应该使用 Mark-Sweep 算法
  • 关注吞吐量的收集器(比如 HotSpot 虚拟机中的 Parallel Old 收集器)应该使用 Mark-Compact 算法

另外,其实还有一种折中的办法,Mark-Sweep 算法速度快,可以让虚拟机平时大多数时间都采用 Mark-Sweep 算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配的时候,再采用 Mark-Compact 算法收集一次,以获得规整的内存空间(基于 Mark-Sweep 算法的 CMS 收集器面临空间碎片过多时采用的就是这种处理办法)


最后放上这道题的背诵版:

🥸 面试官:讲一讲有哪些垃圾收集算法? 😎 小牛肉:主要有三种: 1)标记-清除算法:这是最基础的算法,主要思想就是先标记出所有需要回收的对象,然后统一回收掉所有被标记的对象。 这个算法主要有两个缺点:

  • 执行效率不稳定。如果堆中包含大量对象,而且其中大部分是需要被回收的,这时就必须进行大量的标记和清除的动作,也就是说执行效率和对象数量成反比
  • 内存空间的碎片化问题。标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当程序运行过程中需要分配较大对象时,因无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作

后续两个算法 标记-复制算法 和 标记-整理算法 都是在 标记-清除算法 的基础上做的改进。 2)标记-复制算法:主要思想就是将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次性全部清理掉。 这个半区复制算法也有两个比较明显的问题:

  • 不适用于对象存活率较高的情况(即一般不适用于老生代)
  • 可用内存空间缩小了一半(针对这个问题,“Appel 式回收” 进行了改进,就是根据新生代 “朝生夕灭” 的特点,能够在一轮垃圾收集后活下来的对象少之又少,所以,我们其实并不需要这么大一块的保留区域。具体做法是把新生代分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次分配内存只使用 Eden 和其中一块 Survivor。发生垃圾收集时,在清空之前需要将存活对象复制到另一块 Survivor 中,然后直接清空 Eden 和已用过的那块 Survivor 空间。另外,使用 Apple 式回收的话,还需要有额外的空间进行分配担保,因为我们没有办法百分百保证分配给 To Survivor 的内存空间能够容纳全部的存活对象,常见的做法就是当 To Survivor 空间不足以容纳一次新生代 GC 之后存活的对象时,这些对象便将通过分配担保机制直接进入老年代)

3)标记-整理算法:主要思想就是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。这种移动式的算法相对于非移动式的标记-清除算法来说,吞吐量更高,不过速度相对较慢,因为移动对象需要 Stop the world。所以,关注延迟/速度的收集器(比如 HotSpot 虚拟机中的 CMS 收集器)应该使用 Mark-Sweep 算法,而关注吞吐量的收集器(比如 HotSpot 虚拟机中的 Parallel Old 收集器)应该使用 Mark-Compact 算法。 另外,其实还有一种折中的办法,Mark-Sweep 算法速度快,可以让虚拟机平时大多数时间都采用 Mark-Sweep 算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配的时候,再采用 Mark-Compact 算法收集一次,以获得规整的内存空间(基于 Mark-Sweep 算法的 CMS 收集器面临空间碎片过多时采用的就是这种处理办法)

心之所向,素履以往,我是小牛肉,小伙伴们下篇文章再见 👋

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 飞天小牛肉 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JVM 垃圾收集算法
本文“垃圾收集算法”节选自《深入理解Java虚拟机:JVM高级特性与最佳实践》【作者:周志明】
smartsi
2019/08/07
3390
JVM 垃圾收集算法
Java虚拟机之垃圾收集算法
要想了解Java虚拟机的垃圾收集算法就要知道分代收集理论,因为当前大多数商用垃圾收集算法都是基于分代收集理论进行的。
@派大星
2023/06/28
1870
Java虚拟机之垃圾收集算法
【JVM进阶之路】六:垃圾收集理论和算法
在前面我们了解了虚拟机如何判断对象可回收,接下来我们了解Java虚拟机垃圾收集的一些理论和算法。
三分恶
2021/04/01
3940
【JVM进阶之路】六:垃圾收集理论和算法
看一看JVM垃圾回收算法
Java有着自己一套的内存管理机制,不需要开发者去手动释放内存,开发者只需要写好代码即可,运行过程中产生的垃圾都由JVM回收。那JVM都是用哪些算法进行垃圾回收呢?
索码理
2022/12/28
2790
看一看JVM垃圾回收算法
深入理解JVM(③)各种垃圾收集算法
从如何判定对象消亡的角度出发,垃圾收集算法可以划分为“引用计数式垃圾收集”(Reference Counting GC)和“追踪式垃圾收集”(Tracing GC)两大类,这两类也常被称作“直接垃圾收集”和“间接垃圾收集”。由于束流Java虚拟机中使用 的都是“追踪式垃圾收集”,所以后续介绍的垃圾收集算法都是属于追踪式的垃圾收集。
纪莫
2020/06/12
2810
JVM笔记-垃圾收集算法与垃圾收集器
引用计数法(Reference Counting):为每个对象添加一个引用计数器,用来统计指向该对象的引用个数。当有地方引用它时,计数器加一;引用失效时减一。当某个对象的引用计数为零时,说明该对象已死亡,便可以被回收了。
WriteOnRead
2020/02/24
5250
JVM中垃圾收集算法总结
  通过前面的介绍我们了解了对象创建和销毁的过程。那么JVM中垃圾收集器具体对对象回收采用的是什么算法呢?本文主要记录下JVM中垃圾收集的几种算法。
用户4919348
2019/04/02
4280
JVM中垃圾收集算法总结
Java的垃圾收集机制和作用,以及HotSpot JVM的垃圾收集算法
在Java中,垃圾收集机制(Garbage Collection)是一种自动的管理内存的机制,用于回收不再使用的对象所占的内存空间。
一凡sir
2023/08/13
2680
Java的垃圾收集机制和作用,以及HotSpot JVM的垃圾收集算法
java中垃圾回收机制_垃圾回收机制算法
这一小节先了解一个最基本的问题:如果确定某个对象是“垃圾”?既然垃圾收集器的任务是回收垃圾对象所占的空间供新的对象使用,那么垃圾收集器如何确定某个对象是“垃圾”?通过什么方法判断一个对象可以被回收了。
全栈程序员站长
2022/11/08
5350
java中垃圾回收机制_垃圾回收机制算法
Java虚拟机(四)垃圾收集算法
前言 在本系列上一篇文章中我讲到了垃圾标记算法,垃圾被标记后,GC就会对垃圾进行收集,垃圾收集有很多种算法,这篇文章就来介绍常用的垃圾收集算法的思想。 1.标记-清除算法 标记-清除算法(Mark-S
用户1269200
2018/02/01
6500
Java虚拟机(四)垃圾收集算法
JVM05-垃圾收集算法
这一篇文章我们来熟悉下JVM中各种垃圾回收算法。这些垃圾收集算法是后面各种垃圾收集器的算法基础。闲话少叙,让我们直入主题。
码农飞哥
2021/08/18
2220
JVM的垃圾收集算法
当前商业虚拟机的垃圾收集器,大多数都遵循了 “分代收集”(Generational Collection)的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,分代收集理论它建立在两个分代假说之上:
真正的飞鱼
2023/04/05
3490
JVM之垃圾收集算法
上篇文章我们聊了JVM的内存模型,知道了堆是JMM中最大的一块,也是垃圾收集最急需解决的一块内存区域。今天我们就来唠唠JVM的GC算法。
黑洞代码
2021/01/14
3490
JVM之垃圾收集算法
JVM学习记录-垃圾回收算法
简述 因为各个平台的虚拟机的垃圾收集器的实现各有不同,所以只介绍几个常见的垃圾收集算法。 JVM中常见的垃圾收集算法有以下四种: 标记-清除算法(Mark-Sweep)。 复制算法(Copying)。 标记整理算法(Mark-Compact)。 分代收集算法(Generational Collecting)。  标记-清除算法 标记-清除算法是现代垃圾回收算法的思想基础,主要分为两个阶段:标记阶段和清除阶段。首先根据可达分析算法,标记处可以回收的对象,标记完成后,进行清除阶段,将标记为可回收的对象进行清除。
纪莫
2018/06/14
5690
JVM中垃圾收集算法
垃圾收集是Java虚拟机(JVM)的重要功能之一,它负责自动回收不再使用的内存资源,提高应用程序的性能和可靠性。垃圾收集算法是实现垃圾收集的核心,本文将介绍JVM中常见的垃圾收集算法及其特点。
疯狂的KK
2023/07/02
1420
JVM中垃圾收集算法
Java垃圾回收机制(转)
 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来。在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。顾名思义,垃圾回收就是释放垃圾占用的空间,那么在Java中,什么样的对象会被认定为“垃圾”?那么当一些对象被确定为垃圾之后,采用什么样的策略来进行回收(释放空间)?在目前的商业虚拟机中,有哪些典型的垃圾收集器?下面我们就来逐一探讨这些问题。以下是本文的目录大纲:
Dawnzhang
2018/10/18
3390
Java垃圾回收机制(转)
Java垃圾收集算法介绍
介绍:给对象添加一个引用计数器,每当一个地方引用它时,数据器加1;当引用失效时,计数器减1;计数器为0的即可被回收。
用户1212940
2022/04/13
2430
Java垃圾收集算法介绍
面试官:你对JVM垃圾收集器了解吗?13连问你是否抗的住!
4、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
程序员追风
2020/05/15
2.6K0
面试官:你对JVM垃圾收集器了解吗?13连问你是否抗的住!
垃圾收集算法 Krains 2020-08-06
每个对象保存一个整型的引用计数器,假设有一个对象A,如果别的对象引用了A,就让A对象的引用计数器加1,如果引用失效了,计数器减1,当计数器为0的时候,该对象就是垃圾。
Krains
2020/08/10
2960
垃圾收集算法  Krains 2020-08-06
Jvm数据区域与垃圾收集<深入了解jvm读书笔记>
周志明老师所著的《深入了解JAVA虚拟机》(后文简称”书中”)可谓是java工程师进阶的必读书籍了.最近读了书中的第一二部分,也就是前五章,有很多收获.因此想要写一篇文章.来用自己理解到的知识来总结一下前五章.
呼延十
2019/08/12
4610
相关推荐
JVM 垃圾收集算法
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文