Garbge-Collect-Algrthim
一、为什么要有 GC
1.1 什么是 GC
GC 是 Garbage Collection 的简称,中文称为“垃圾回收”。GC ,是指程序把不用的内存空间视为垃圾并回收掉的整套动作。
GC 要做的有两件事:
-
找到内存空间里的垃圾;
-
回收垃圾,让程序能再次利用这部分空间。
满足这两项功能的程序就是 GC。
1.2 为什么要有 GC
在没有 GC 的世界里,程序员必须自己手动进行内存管理,必须清楚地确保必要的内存空间,释放不要的内存空间。
程序员在手动进行内存管理时,申请内存尚不存在什么问题,但在释放不要的内存空间时,就必须一个不漏地释放。这非常麻烦,容易发生下面三种问题:内存泄露,悬垂指针,错误释放引发 BUG。
-
如果忘记释放内存空间,该内存空间就会发生内存泄露(内存空间在使用完毕后未释放),即无法被使用,但它又会持续存在下去。如果将发生内存泄露的程序放着不管,总有一刻内存会被占满,甚至还可能导致系统崩溃。
-
在释放内存空间时,如果忘记初始化指向已经回收的内存地址(对象已释放)的指针,这个指针就会一直指向释放完毕的内存空间。此时,这个指针处于一种悬挂的状态,我们称其为“悬垂指针”(dangling pointer)。如果在程序中错误地引用了悬垂指针, 就会产生无法预期的 BUG。
-
一旦错误释放了使用中的内存空间,下一次程序使用此空间时就会发生故障。大多数情况下会发生段错误,运气不好的话还可能引发恶性 BUG,甚至引发安全漏洞。
为了省去上述手动内存管理的麻烦,人们钻研开发出了 GC。如果把内存管理交给计算机, 程序员就不用去想着释放内存了。
当然,技术领域的不变法则就是万事皆有代价,GC 也会带来一些麻烦,比如后台程序需要耗费一定的 CPU 和内存资源去释放内存,在系统繁忙的情况下会对业务程序性能造成一定的不利影响。为了解决 GC 带来的问题,最近几年出现了一门新的没有 GC 的 Rust 语言,大有替代 C 语言的趋势,不过学习曲线比较陡峭,感兴趣的同学可以自行钻研。
二、GC 相关的基本术语
2.1 对象、指针、活动对象、非活动对象、堆、根
GC 操作的基本单元可以叫做对象。对象是内存空间的某些数据的集合。在本文中,对象由头(header)和域(field)构成。
对象的头,主要包含对象的大小、种类信息。对象中可访问的部分称为“域”,可以认为是 C 语言中结构体的成员变量。如图2.1所示。
图2.1 对象、头、域
指针是指向内存空间中某块区域的值。GC 是根据对象的指针指向去搜寻其他对象的。对象和指针的关系如图2.2所示。
图2.2 对象和指针
我们将内存空间中被其他对象通过指针引用的对象成为活动对象,没有对象引用的对象是非活动对象,也就是 GC 需要回收的垃圾。如图2.3所示。
图2.3 活动对象和非活动对象
根(root)是“根基”“根底”。在 GC 的世界里,根是指向对象的指针的“起点” 部分。堆指的是用于动态(也就是执行程序时)存放对象的内存空间。当应用程序申请存放对象时, 所需的内存空间就会从这个堆中被分配给 应用程序。如图2.4所示。
图2.4 根和堆里的对象
2.2 GC 算法性能的评价标准
评价 GC 算法的性能时,我们采用以下 4 个标准。
-
吞吐量。
-
最大暂停时间。
-
堆使用效率。
-
访问的局部性。
吞吐量
GC 的吞吐量是:运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。
如图2.5所示,在程序运行的整个过程中,应用程序的时间是 X、Y、Z,应用程序的总时间是 X + Y + Z。GC 一共启动了两次,花费的时间为 A、B,则 GC 总花费的时间是 A + B。这种情况下 GC 的吞吐量为 (X + Y + Z) /(X + Y + Z + A + B)。
图2.5 应用程序和 GC 的执行示意图
从这里的描述可知,GC 的吞吐量就是应用程序执行的时间(不是内存大小哦)和 GC 时间的比值,GC 执行的总时间越短,GC 吞吐量越大。
人们通常都喜欢吞吐量高的 GC 算法。然而判断各算法吞吐量的好坏时不能一概而论。因为工程技术中,任何好处都是有代价的。
最大暂停时间
本文介绍的所有 GC 算法,都会在 GC 执行过程中另应用程序暂停执行。最大暂停时间指的是“因执行 GC 而暂停执行应用程序的最长时间”。
当编写像动作游戏这样追求即时性的程序时,就必须尽量压低 GC 导致的最大暂停时间。如果因为 GC 导致玩家频繁卡顿,相信谁都会想摔手柄。对音乐和动画这样类似于编码应用的程序来说,GC 的最大暂停时间就不那么重要了。更为重要的是,我们必须选择一个整体处理时间更短的算法。
不管尝试哪种 GC 算法,我们都会发现较大的吞吐量和较短的最大暂停时间不可兼得。所以应根据执行的应用所重视的指标的不同,来分别采用不同的 GC 算法。
堆使用效率
堆使用效率,是指:程序在运行过程中,单位时间内能使用的堆内存空间的大小。举个例子,GC 复制算法中将堆二等分,每次只使用一半,交替进行,因此总是只能利用堆的一半。相对而言,GC 标记-清除算法和引用计数法就能利用整个堆。