分代垃圾回收(Mark-Sweep GC),并不是一个具体的算法,只是结合了几种垃圾回收算法,把对象按特点进行了分类,对每种特点的对象集执行不同的回收算法,从而提升回收效率

阅读本文之前,你最好已经了解了复制算法和标记清除算法,因为文中不会过多重复介绍复制算法和清除算法的内容

分代垃圾回收在对象中引用了 “年龄” 的概念,通过优先回收容易称为垃圾的对象,从而提高垃圾回收的效率。大部分的对象在生成后马上就变成了垃圾, 很少有对象能活得很久。

分代垃圾回收利用这个经验,在对象中加入了 “年龄” 的概念,经理过一次 GC 后还活下来的对象年龄为 1 岁。

分代垃圾回收中把对象分为几代(generation),针对不同的代使用不同的 GC 算法;把新生成的对象称为年轻代 (Young Generation) 对象,到达一定年龄的对象称为老年代 (Old/Tenured  Generation) 对象。

由于大多数对象都是 “朝生夕死” 的,所以可以考虑对年轻代进行 “只标记存活对象” 的算法,因为存活对象较少,所以回收效率高。

年轻代 GC 称为 Minor GC。经历多次年轻代 GC 仍然存活的对象,就可以当作老年代对象来处理。这种年轻代转移到老年代的情况称为晋升(promotion)。

因为老年代对象很难成为垃圾(经过几次 GC 还存活的对象,一般都是都是永久存活了),所以老年代 GC 的频率会很低,老年代 GC 称为 Major GC。

本文参考的是 David Ungar 在 1984 年提出的算法,基于 C 语言

名词解释

对象

对象在 GC 的世界里,代表的是数据集合,是垃圾回收的基本单位。

指针

可以理解为就是 C 语言中的指针(又或许是 handle),GC 是根据指针来搜索对象的。

mutator

这个词有些地方翻译为赋值器,但还是比较奇怪,不如不翻译……mutator 是 Edsger Dijkstra 琢磨出来的词,有 “改变某物” 的意思。说到要改变什么,那就是 GC 对象间的引用关系。不过光这么说可能大家还是不能理解,其实用一句话概括的话,它的实体就是“应用程序”。

mutator 的工作有以下两种:生成对象

更新指针mutator 在进行这些操作时,会同时为应用程序的用户进行一些处理(数值计算、浏览网页、编辑文章等)。随着这些处理的逐步推进,对象间的引用关系也会 “改变”。伴随这些变化会产生垃圾,而负责回收这些垃圾的机制就是 GC。

GC ROOTS

GC ROOTS 就是引用的起始点,比如栈,全局变量

堆 (Heap)

堆就是进程中的一段动态内存,在 GC 的世界里,一般会先申请一大段堆内存,然后 mutatar 在这一大段内存中进行分配

活动对象和非活动对象

活动对象就是能通过 mutatar(GC ROOTS)引用的对象,反之访问不到的就是非活动对象。

准备工作

首先是对象类型的结构:

为了动态访问 “对象” 的属性,此处使用属性偏移量来记录属性的位置,然后通过指针的计算获得属性

然后是对象的结构,虽然 C 语言中没有继承的概念,但是可以通过共同属性的 struct 来实现:

算法实现

在分代垃圾回收中,堆的结构如下图所示。将堆分成了 4 个部分,从左至右分别是新生成区 (Eden),两个大小相等的幸存空间 (Survivor)From/to,以及一个老年代区 (Old Gen),Eden+Survivor 都属于年轻代区域(New Gen)。

年轻代对象会分配在年轻代区域,老年代对象会分配在老年代。

此处还额外准备了一个记录集(Remembered set),来存储跨代的引用(跨代引用下面会介绍)

年轻代 GC(Minor GC)

由于新生代对象特点是 “朝生夕死”,所以对年轻代使用复制算法;Eden 区存放的是新生成的对象,当 Eden 满了之后,年轻代 GC 就会启动,将生成空间的所有活动对象复制,不过目标区域是 Survivor 区。

Survivor 区分为了两个空间,每次回收只会使用其中的一个。当执行年轻代 GC 的时候,Eden 区的活动对象会被复制到 From 中;当第二次年轻代 GC 时,会将 Eden 和 From 区内存活的对象一起复制到 To 区,之后再把 From/To 功能 “互换”(这里的互换并没有互换数据,在程序中只是把引用换了)

下面是 From/To 互换的逻辑,只是将指针互换了以下而已:

具体 “互换” 流程如下图所示:

对象晋升 (Promotion)

对象中有一个 age 字段,代表对象经历的年轻代 GC 次数,新创建的对象年龄为 0,每经历一次年轻代 GC 还存活的对象年龄会加 1;在年轻代 GC 时,每次会检查对象的年龄,当超过一定限制(AGE_MAX)时,会将对象晋升到老年代。

以下是晋升老年代的处理:

详细晋升流程如下图所示(图中包含了基本的年轻代 GC 过程):

跨代引用

既然有晋升的操作,那么这里会有一个问题:当对象晋升后,引用关系如何处理,对于老年代到年轻代的引用,可达性分析时怎么处理,是否还需要从 GC ROOTS 开始遍历老年代呢?

比如对象 A 晋升前,和年轻代另一个存活的对象 B 关联,A 在 GC ROOTS 中,B 不在;当对象 A 晋升后,对于 GC ROOTS 来说 B 是不可达(unreachable)的,但是对于 A 来说 B 是可达的

或者对象 A 晋升后,又新分配了对象 C,然后用 A 引用 C,此时对于 GC ROOTS 来说,C 也是不可达的

由于存在跨代引用的可能,所以在年轻代 GC 时,只从 GC ROOTS 开始遍历年轻代对象是不够的,还需要将老年代中引用年轻代的那部分对象也作为 GC ROOTS,这样才能保证完整的回收年轻代

扫描老年代这部分对象看起来没问题,可是由于老年代的特点是长期存活的对象,空间很大对象很多,扫描老年代的成本要远远大于扫描 GC ROOTS,成本太高,所以直接从 GC ROOTS 遍历老年代或者顺序遍历老年代的 free-list 不合适。

记录集合(Remembered set)

跨代引用这个问题可以以空间换时间的方式,使用一个额外的数组来存储跨代引用的关系:

如上图所示,使用一个额外的 Remembered Set 来存储引用着年轻代那部分的老年代对象,当发生年轻代 GC 时,除了要遍历 GC ROOTS 中那部分年轻代对象,还要遍历 Remembered Set 中的这部分存在跨代引用的对象,这样就避免了额外扫描老年代的问题

至于这个 Remembered Set 的添加时机也很简单,只需要在发生晋升时检查晋升对象是否还包含年轻代对象的引用即可,如果包含就将晋升的对象添加到 Remembered Set。

本文中只做了最简单的实现,当晋升后的对象还保留年轻代的引用时,手动添加到 Remembered Set

手动更新引用:若老年代对象的新引用是年轻代对象,则添加到 Remembered Set

自动更新引用:对晋升的对象执行 "write_barrier",检查是否存在跨代引用,若存在则添加到 Remembered Set

这个额外的添加操作看着有点傻,而且如果跨代引用过多还可能会导致 RS 溢出,所以有另一种替代 Remembered Set 的方式:页面标记(Card Marking),由于本文没有实现,这里就不做介绍了

老年代 GC(Major GC)

由于老年代对象的特点是长期存活,所以老年代空间一般会比年轻代大很多。而且基于这个长期存活的特点,老年代并不适合复制算法,因为复制算法需要频繁移动对象,且复制算法效率取决于存活对象数量;所以老年代会使用标记 - 清除算法,和基本的清除算法一样,具体参考之前的文章《垃圾回收算法实现之 - 标记 - 清除(完整可运行 C 语言代码)》

优点

“大部分的对象在生成后马上就变成了垃圾, 很少有对象能活得很久。” 这一说法虽然并不绝对,但还是可以适应绝大多数场景的。以这个理论为前提,新生代 GC 只会扫描新生代空间的对象,这样就可以减少 GC 的时间消耗

而且一般年轻代空间会设置的较小,而且是复制算法效率极高,所以就算新生代 GC 频繁,在时间的消耗上一般也是可以介绍的;老年代清除算法效率低且空间大,但是由于老年代对象的特点是长期存活,所以老年代的 GC 频率会很低。

综合来看,分代回收之后,会大幅改善 GC 所消耗的时间

缺点

“大部分的对象在生成后马上就变成了垃圾, 很少有对象能活得很久。” 这个原则毕竟只适合大多数情况,不可能适用所有程序,所以如果出现不匹配的场景,就可能会导致以下问题:年轻代 GC 后不可达对象极少,导致复制对象过多造成耗时增加

老年代被提前填满,导致老年代 GC 频繁

完整代码

相关文章

参考《垃圾回收的算法与实现》 中村成洋 , 相川光 , 竹内郁雄 (作者) 丁灵 (译者)

《垃圾回收算法手册 自动内存管理的艺术》 理查德·琼斯 著,王雅光 译

heap c语言数组实现,垃圾回收算法实现之 - 分代回收(完整可运行C语言代码)...相关推荐

  1. 【JVM】垃圾回收算法与分代回收

    文章目录 1. 垃圾回收算法概述 2. 标记-清除算法 3. 标记-复制算法 4. 标记-整理算法 5. 分代回收 本文参考:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 1. 垃圾回收 ...

  2. JVM分代回收机制和垃圾回收算法

    JVM系列文章目录 初识JVM 深入理解JVM内存区域 玩转JVM对象和引用 JVM分代回收机制和垃圾回收算法 细谈JVM垃圾回收与部分底层实现 Class文件结构及深入字节码指令 玩转类加载和类加载 ...

  3. java垃圾回收 分代_JVM基础知识(四)- 分代回收机制和垃圾回收算法

    这次我们来讲讲垃圾回收,前边或多或少的都提及过垃圾回收的知识点,我们经常说的GC(Garbage Collection)就是垃圾回收,我们都知道JAVA都是由C++演化而来,那么JAVA和C++很重要 ...

  4. Python的垃圾回收机制(引用计数+标记清除+分代回收)

    一.写在前面: 我们都知道Python一种面向对象的脚本语言,对象是Python中非常重要的一个概念.在Python中数字是对象,字符串是对象,任何事物都是对象,而它们的核心就是一个结构体--PyOb ...

  5. 【JVM学习笔记】内存回收与内存回收算法 就哪些地方需要回收、什么时候回收、如何回收三个问题进行分析和说明

    目录 一.相关名词解释 垃圾收集常用名词 二.哪些地方需要回收 本地方法栈.虚拟机栈.程序计数器 方法区 Java堆 三.什么时候回收 1. 内存能否被回收 内存中的引用类型 引用计数算法 可达性分析 ...

  6. 垃圾分代回收机制简单介绍

    针对GC的简单介绍 JVM对自己的内存进行了划分5个区域,分别是堆,栈,方法区,本地方法栈,程序计数器.Java中对每一种类型都规定了具体的不可变的大小.所以所有的内存都是由JVM自动分配,所有的内存 ...

  7. python分代回收_python 垃圾回收——分代回收 和java有些区别 注意循环引用无法被回收...

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 语言的内存管理是语言设计的一个重要方面.它是决定语言性能的重要因素.无论是C语言的 ...

  8. jvm垃圾回收机制_JVM 垃圾回收机制之堆的分代回收

    JVM垃圾回收机制之堆的分代回收 前言 前文我们了解了Java的GC机制,对于堆中的对象,JVM采用引用计数和可达性分析两种算法来标记对象是否可以清除,本文中我们还会了解到JVM将对分成了不同的区域, ...

  9. c语言代码大全复制,垃圾回收算法实现之 - 复制算法(完整可运行C语言代码)...

    GC 复制算法(Copying GC)是 Marvin L. Minsky 在 1963 年研究出来的算法.说得简单点,就是只把某个空间里的活动对象复制到其他空间,把原空间里的所有对象都回收掉.这是一 ...

最新文章

  1. 数字开头的正则表达式_初学Web前端要注意什么 正则表达式是怎么回事
  2. [数据结构]邻接矩阵和邻接表存储的图DFS,BFS算法时间复杂度分析
  3. 【转载保存】RunTime.getRunTime().addShutdownHook 添加钩子
  4. 两个瓶子水怎样一样多_同事每天比我多睡两个小时!省下70万买了地铁站附近房子 杭州姑娘却感叹买房时一定是脑子进了水……...
  5. 用c/c++实现linux下检测网络接口状态【ZT】
  6. C# .Net中的类型转换(3)
  7. 服务器2008r2网络禁止修改,windows-server-2008-r2 – Windows 2008 R2标准服务器 – 如何禁用RC4...
  8. @AuotoWired+@Qualifier(百度百科)
  9. 企业架构之道(三)之企业架构框架概述
  10. 华为盒子 鸿蒙,华为盒子真不值得买,网友总结了3个原因
  11. 计算机组成原理MIPS
  12. 如何应对团队协作的五大障碍
  13. linux网易云音乐安装失败需要×××依赖
  14. 网易云音乐人申请教程(会唱歌即可)
  15. 家用监控摄像机录制视频上的时间水印有什么用
  16. 优秀课程案例|如何用scratch画扇形统计图
  17. 【飞桨】Seg:U-Net【2015 MICCAI】论文研读
  18. 关于ASP.Net的validateRequest=false(验证请求)
  19. linux多个文件删除重复行,shell中删除文件中重复行的方法
  20. 稳压二极管工作原理、重要参数意义和典型电路参数计算

热门文章

  1. thinkjs——两表联查
  2. 如何通过link_to传递一个post请求
  3. 使用jquery datatables插件遇到fnReloadAjax的问题
  4. 利用IIS建立高安全性Web服务器
  5. 大屏监控系统实战(10)-大屏展示前20个博主的排名、票数及名次相对于前一日的升降情况
  6. 网络(13)-SYN flood及其应对方法
  7. linux安装软件时提示找不到镜像的问题:Couldn't resolve host 'mirrorlist.centos.org'
  8. 步步深入MySQL:架构-gt;查询执行流程-gt;SQL解析顺序!
  9. 无需埋点的移动数据分析平台GrowingIO V1.0
  10. EIGRP负载均衡实验(如有疑问,请留言)