[自动内存管理]术语和记号
1. 堆和对象
堆是由一段或者几段连续内存组成的空间集合。内存颗粒(granule)是堆内存分配的最小单位,一般是一个字(word)或者一个双字(double-word),这取决于需要对齐的方式。内存块(chunk)是一组较大的连续内存颗粒。内存单元(cell)是由数个连续颗粒组成的小内存块,通常用于内存的分配和释放,也可能由于某种原因被浪费或闲置。堆通常可以使用对象图的方式来描述,它是一个有向图,图的节点是堆中的对象,有向边是对象之间的引用。
对象(object)是为应用程序分配的内存单元。对象通常是一段可寻址的连续字节或字的数组,其内部被划分为多个槽(slot)或者域(field),如下图所示:
域可能会包含一些非引用的纯数据,例如整数。引用可以是指向某个堆中对象的指针,也可以是空。引用通常是一个正规指针,指向对象头部(即对象首地址)或者距离头部有一定偏移量的位置。某些情况下,对象会用一个对象头来存放运行时系统会用到的元数据,它一般位于对象的起始地址。派生指针一般是在对象的正规指针的基础上增加一个偏移量而得到的指针,而内部指针是指向内部对象域的派生指针。
内存块是依照特定大小(通常是2的整数次幂)对齐的大块内存。页是由处理器以及操作系统的虚拟内存机制定义的,高速缓存行是由CPU的高速缓存定义的。
2. 赋值器与回收器
对于使用垃圾回收的程序,将其执行过程划分为两个半独立的部分:
- 赋值器执行应用代码。这一过程会分配新对象,并且修改对象之间的引用关系,进而改变对象图的拓扑结构;引用域可能是堆中对象,也可能是根,例如静态变量或线程栈等。随着引用关系的不断变更,部分对象会失去与根的联系,即从根出发,沿着对象图的任何一条边进行遍历都无法到达该对象。
- 回收器执行垃圾回收代码。这一过程将找到不可达对象并将其回收。
一个程序可能拥有多个赋值器线程,但是它们共用同一个堆。相应的,也可能存在多个回收器线程。赋值器线程在工作过程中会执行三种与回收器相关的操作:创建新对象,读取对象的某个属性域,更新对象的某个属性域。某些特殊的内存管理器会为基本操作增加一些额外的功能,即屏障。屏障操作会同步或者异步地与回收器产生交互。在后面,我们会区分读屏障和写屏障。
3. 赋值器根
与堆内存不同,赋值器的根是一个有限的指针集合,赋值器可以不经过其它对象直接访问这些指针。堆中直接由赋值器根所引用的对象称为根对象。当赋值器访问堆中的对象时,它需要从当前的根对象集合中加载指针(进而会加载新的根)。赋值器也可能会丢弃一些根,例如让某个根指针指向新的引用。在实际情况下,根通常包括全局变量以及线程的栈帧。我们用Roots
标识根集合。
在类型安全的语言中,一旦某个对象在堆中不可达,并且赋值器的所有根指针中也不包含对该对象的引用,赋值器将无法再次访问该对象。在没有与运行时系统交互的情况下,赋值器不能再随意地“重新发现”该对象,因为它已经不能通过任何指针找到该对象,也无法在该对象的地址上构造新对象。
4. 分配器
分配器负责两项工作:分配内存,以及释放内存。所谓分配内存,就是找到一块内存空间,并将它绑定到一个对象上;所谓释放内存,就是将之前分配出的内存归还给分配器,以便下次重复利用。