垃圾回收基本概念
Basic garbage collection concepts
计算机中垃圾回收的故事
声明:本文是个人读书的感悟和知识总结。其中有一些感性认识,如果感兴趣请留言,有错误也请指正。
要想认清垃圾回收最好在大脑中有了堆栈模型的情况下,了解了操作系统程序运行机制,再去认识垃圾回收就会更加深刻和彻底。(个人感悟)
✓ 0x00 垃圾回收基础概念-【对象 /头 /域】
对象这个词,在不同的使用场合其意思各不相同。在面向对象编程中,它指“具有属性和行为的事物”,然而在 GC 的语境中,对象表示的是“通过应用程序利用的数据的集合”。
· 对象配置在内存空间里。GC 根据情况将配置好的对象进行移动或销毁操作。
· 对象是 GC 的基本单位。
· 对象由头(header)和域(field)构成。
1. 对象的头
对象中保存对象本身信息的部分称为“头”。头主要含有以下信息:
· 对象的大小
· 对象的种类
2. 对象的域
我们把对象使用者在对象中可访问的部分称为“域”。可以将其想成 C 语言中结构体的成员。对象使用者会引用或替换对象的域值。另一方面,对象使用者基本上无法直接更改头的信息。域中的数据类型大致分为以下 2 种:
· 对象的大小
· 对象的种类
✓ 0x01 垃圾回收基础概念-【指针】
通过 GC,对象会被销毁或保留。这时候起到关键作用的就是指针。因为 GC 是根据对象的指针指向去搜寻其他对象的。另一方面,GC 对非指针不进行任何操作。
· 要注意语言处理程序是否能判别指针和非指针。
· 指针要指向对象的哪个部分。指针如果指向对象首地址以外的部分,GC 就会变得非常复杂。
✓ 0x02 垃圾回收基础概念-【mutator】
mutator 是 Edsger Dijkstra琢磨出来的词,有“改变某物”的意思。说到要改变什么,那就是 GC 对象间的引用关系。不过光这么说可能大家还是不能理解,其实用一句话概括的话,它的实体就是“应用程序”。这样说就容易理解了吧。GC 就是在这个 mutator 内部精神饱满地工作着。mutator 实际进行的操作有以下 2 种。
· 生成对象
· 更新指针
✓ 0x03 垃圾回收基础概念-【堆】
堆指的是用于动态(也就是执行程序时)存放对象的内存空间。当 mutator 申请存放对象时,所需的内存空间就会从这个堆中被分配给 mutator。
GC 是管理堆中已分配对象的机制。在开始执行 mutator 前,GC 要分配用于堆的内存空间。一旦开始执 mutator,程序就会按照** mutator** 的要求在堆中存放对象。等到堆被对象占满后,GC 就会启动,从而分配可用空间。如果不能分配足够的可用空间,一般情况下我们就要扩大堆。
✓ 0x04 垃圾回收基础概念-【活动对象 /非活动对象】
将分配到内存空间中的对象中那些能通过 mutator 引用的对象称为“活动对象”。反过来,把分配到堆中那些不能通过程序引用的对象称为“非活动对象”。也就是说,不能通过程序引用的对象已经没有人搭理了,所以死掉了。死掉的对象(即非活动对象)我们就称为“垃圾”。GC 会保留活动对象,销毁非活动对象。当销毁非活动对象时,其原本占据的内存空间会得到解放,供下一个要分配的新对象使用。
✓ 0x05 垃圾回收基础概念-【分配】
分配(allocation)指的是在内存空间中分配对象。当 mutator 需要新对象时,就会向分配器(allocator)申请一个大小合适的空间。分配器则在堆的可用空间中找寻满足要求的空间,返回给 mutator。
当堆被所有活动对象占满时,就算运行 GC 也无法分配可用空间。为了处理如上问题:
1. 销毁至今为止的所有计算结果,输出错误信息
2. 扩大堆,分配可用空间
✓ 0x06 垃圾回收基础概念-【分块】
分块(chunk)在 GC 的世界里指的是为利用对象而事先准备出来的空间。
初始状态下,堆被一个大的分块所占据。然后,程序会根据 mutator 的要求把这个分块分割成合适的大小,作为(活动)对象使用。活动对象不久后会转化为垃圾被回收。此时,这部分被回收的内存空间再次成为分块,为下次被利用做准备。也就是说,内存里的各个区块都重复着分块→活动对象→垃圾(非活动对象)→分块→……这样的过程。
PS. 类似于操作系统内存分配优化
✓ 0x07 垃圾回收基础概念-【根】
根(root)这个词的意思是“根基”“根底”。在 GC 的世界里,根是指向对象的指针的“起点”部分。
✓ 0x08 垃圾回收基础概念-【垃圾回收评价标准】
评价 GC 算法的性能时,通常采用以下 4 个标准。
• 吞吐量
• 最大暂停时间
• 堆使用效率
• 访问的局部性
1.吞吐量
从一般意义上来讲,吞吐量(throughput)指的是“在单位时间内的处理能力”。
在 mutator 整个执行过程中,GC 一共启动了 3 次,我们把花费的时间分别设为 A、B、C。
也就是说,GC 总共花费的时间为(A + B + C)。同时,以 GC 为对象的堆大小是 HEAP_SIZE。也就是说,在大小为 HEAP_SIZE 的堆进行内存管理,要花费的时长为(A + B + C)。因此,这种情况下 GC 的吞吐量为HEAP_SIZE /(A + B + C)。
2.最大暂停时间
几乎所有 GC 算法,都会在 GC 执行过程中令 mutator 暂停执行。最大暂停时间指的是“因执行 GC 而暂停执行 mutator 的最长时间”。
PS. 可以认为 GC 和检活是两个阶段。暂停会影响程序效率,结论就是最大暂停时间越小越好。
3.堆使用效率
根据 GC 算法的差异,堆使用效率也大相径庭。左右堆使用效率的因素有两个。
一个是头的大小,另一个是堆的用法。
首先是头的大小。在堆中堆放的信息越多,GC 的效率也就越高,吞吐量也就随之得到改善。但毋庸置疑,头越小越好。因此为了执行 GC,需要把在头中堆放的信息控制在最小限度。
其次,根据堆的用法,堆使用效率也会出现巨大的差异。举个例子,GC 复制算法中将堆二等分,每次只使用一半,交替进行,因此总是只能利用堆的一半。相对而言,GC 标记 -
清除算法和引用计数法就能利用整个堆。结论就是:可用的堆越大,GC 运行越快;相反,越想有效地利用有限的堆,GC 花费的时间就越长。
4.访问的局部性
具有引用关系的对象之间通常很可能存在连续访问的情况。这在多数程序中都很常见,称为“访问的局部性”。考虑到访问的局部性,把具有引用关系的对象安排在堆中较近的位置,就能提高在缓存中读取到想利用的数据的概率,令 mutator 高速运行。
参考文献
[1] 垃圾回收的算法与实现 [日] 中村成洋