《垃圾收集》笔记——第一章

来源:互联网 发布:淘宝p图神器软件 编辑:程序博客网 时间:2024/04/30 09:55

第一章


1.1 内存分配的历史

计算机发展的早期,程序员和计算机的交流建立在二进制位上,通过简单的开关来控制。下一步允许程序员使用易于记忆的代码,这些代码可以机械的翻译成二进制记号。

为了确定是否有足够的空间载入程序,同时为了指定跳转的目标,程序员必须特别注意计算程序有多少字长以及寻找指令的绝对地址。

40年代末50年代初,表示地址和操作符的数字码被换成了更容易理解的符号化代码。然而程序员必须密切关心特定的计算机如何操作,数据在计算机中如何表示、放在哪里。

1957年,第一个Fortran编译器发布。

高级程序设计语言的编译器必须分配目标计算机资源以表示用户程序操纵的数据对象。

1.1.1 静态分配
过程的局部变量在过程的每个活动中都绑定在同一位置。
3个局限:
 1)每个数据结构的大小必须在编译时可知。
 2)过程不能是递归的,因为对于过程的每个活动,局部的名字在内存中共享相同的位置。
 3)无法动态地创建数据结构。
2个优点:
 1)效率较高,因为不需要在程序执行时创建或销毁栈帧(stack frame)等数据结构。
 2)安全:程序不可能因为耗尽内存而运行失败,因为它的内存需求可以预知。

1.1.2 栈分配
每次过程调用时,一个活动记录(activation record)或是帧被压入系统栈,并在返回时弹出。
5个特点:
1)同一过程的不同活动中,局部变量不再共享相同的绑定。递归调用成为可能,从而大大地增强了语言的表达能力。
2)像数组这样的局部数据结构,其大小可能取决于传递给过程的参数。
3)栈分配的局部名字的值,无法从一个活动保持到下一个活动。
4)被调用者的活动记录不可能比它的调用者寿命更长。
5)只有大小能在编译时确定的对象,才能作为过程的结果返回。

1.1.3 堆分配
堆中的数据结构能够以任意次序分配与释放。因而活动记录和动态数据结构可能比创建它们的过程更长寿。
特点:
1)设计就是通过创建抽象为客观世界的问题建模,而这些抽象中,许多天生具有层次结构:链表和树。堆分配使得这类抽象的具体表示可以是递归的。
2)数据结构的大小不再固定,而是可以动态变化。数组下标越界,是程序失败最常见的根源之一。
3)动态大小的对象可以作为过程的结果返回。
4)许多程序设计语言允许把一个过程作为另一个过程的结果返回。

 

1.2 状态、存活性和指针可到大性
程序可以直接操纵下列3种位置中的值:寄存器、程序栈(包括局部变量和临时变量)和全局变量。这些位置中,有一部分保存了指向堆数据的引用,它们构成了根(root)集合。

对于堆中的一个独立分配的数据片断,我们可以等价地称之为节点(node)、单元(cell)或是对象(object)。这一规则意味着,从存储机制的角度看,堆中对象构成的图的存活性(liveness)由指针可达性(pointer reachability)所定义。堆中对象存活的条件是:某个根保存着它的地址,或是另外存在一个存活的堆节点保存了指向它的指针。

确定一个节点的存活性的两种方法:
1)直接的方法需要位堆中每一个节点关联一份记录,记录其他堆节点或是根到该节点的引用。最常见的方法是在这个单元内部保存指向这个单元的指针的数量,也就是它的引用计数值(reference count)。
2)间接或者说追踪的收集器的典型做法,则是在每次用户程序请求更多内存失败时重新生成存活节点的集合。收集器从根出发,沿着指针链,访问所有可到达的节点。

 

1.3 显式堆分配
传统上,大多数命令式语言将分配与释放堆中对象的责任交给程序员。

1.3.2 垃圾
动态分配的内存可能变得无法访问。那些不再存活但仍未释放的对象,就称作垃圾(garbage)。垃圾是无法通过显式释放来重新利用的,因为它所占据的空间泄漏(leak)了。如链表的第1项可以被访问,第1项的next置为空,则2、3项就成为垃圾。

1.3.3 悬挂引用
内存也可能在仍然存在指向它的引用时被释放。

把第2项归还给堆管理器,第3项再次变成垃圾,这个微小的泄漏不会危害我们的小程序。然而,第1项的next域指向一块已经被释放的内存。一个悬挂引用(dangling reference)出现了。

程序无法控制释放了的内存的用途。它可能被清空,也可能被用来保存簿记信息或被堆管理器回收使用。如果程序访问悬挂引用,我们所能期望的最好结果是立刻崩溃。如果堆管理器把释放的内存重新分配给了程序中德其他数据结构,同一个位置将代表两个不同的对象。如果我们够幸运,程序将在未来的某一天崩溃,否则,程序可能继续运行但产生的结果却是错误的。

1.3.4 共享
解决上述两个问题的方法是让这两个动作(销毁最后一个引用和释放目标对象)同时发生,但是在存在共享的情况下并不容易。
假设两个链表共享一个相同的后缀。在指向链表表头的指针销毁时,一个行为良好的链表清理过程将会递归地释放了链表中德每一项。然后,若cat和mat中德一个以这种方式销毁,另一个将只剩下一个元素和一个悬挂引用。

 

1.4.3 软件工程的课题
对软件工程最简洁的描述就是:对大规模软件系统中的复杂性的管理。抽象和模块化则是软件工程最有力的工具。

以为作者曾经指出,在没有垃圾收集的情况下,复杂系统的内存管理问题不可能得到解决,除非程序在设计时就把正确管理内存作为其主要目标。另一方面,垃圾收集将内存管理问题从类的接口中剥离,而不是把它散布到所有的代码中去。

原创粉丝点击