跳至内容

Ruby 黑客指南/垃圾回收

来自 Wikibooks,开放的书,为开放的世界

程序的运行时结构

[编辑 | 编辑源代码]

在深入本章内容之前,让我们回顾一下程序执行时内存的组织方式。本章将涉及计算机体系结构的一些底层组件,因此事先熟悉一些基本概念是必要的。此外,这些概念也将在后面的章节中用到。让我们在这里把它们解决掉。

大多数 C 程序在它们的内存空间中都有以下组件

  1. 文本区域,
  2. 静态和全局变量的存储,
  3. 机器栈,
  4. 堆。

文本区域是存储代码的地方。第二个组件应该很清楚。机器栈是存放函数的参数和局部变量的地方。堆是通过malloc()分配的。

让我们详细讨论一下机器栈。被称为机器“栈”,它显然具有类似栈的结构。换句话说,我们可以不断地向顶部添加新元素。在实践中,值以单独的int单元添加到堆栈中,但在概念上有一个称为堆栈帧的较大单元。

每个堆栈帧对应一次函数调用。换句话说,每个函数调用都会添加一个堆栈帧,并且在return时会移除一个堆栈帧。如果我们极度简化这一点,机器栈可能看起来像图 1。

 the top
+-------------+
| stack frame | <-- the frame for the currently running function
+-------------+
| stack frame |
+-------------+
| stack frame |
+-------------+
| stack frame |
+-------------+
| stack frame |
+-------------+
 the bottom

图 1:机器栈

在这张图中,我们标记了栈的极端端为“顶部”,但机器栈并不一定从低到高地寻址帧。例如,在 x86 机器上,栈从较高地址向较低地址增长。

使用malloc()可以分配任意大小的内存。alloca()是机器栈的版本。但是,通过alloca()分配的内存不需要释放。或者,更确切地说,可以更好地说是内存随着函数的return而“被”释放。因此,alloca()分配的值不能用作函数的return值。这与“不能返回指向局部变量的指针”相同。

所有这一切都很好。它基本上意味着我们可以本地分配长度动态变化的数组。

但是,有一些环境没有本机alloca()。许多人会希望在这些环境中也使用alloca(),因此可以用 C 编写具有相同行为的函数。但是,在这种情况下,它可能只被实现为“不需要释放”,但可能不一定会分配机器栈上的内存。事实上,它通常不会这样做。如果它能够做到这一点,那么可能也存在alloca()的本机实现。

我们如何在 C 中实现alloca()?最直接的实现首先使用malloc()分配内存。然后,它将调用者函数和分配的地址存储在全局列表中。然后,下次调用alloca()时,如果存在为已经结束的函数分配的任何内存,就可以将其free()掉(见图 2)。

+-----------+      +------------+
| main      |      | main       |
+-----------+      +------------+
| A         | ===> | A          |
+-----------+      +------------+
| B         |      | B          | mark that B -> alloca(32)
+-----------+      | alloca(32) | free the memory allocated for D
| C         |      +------------+
+-----------+
| D         |
| alloca(8) | mark that D -> alloca(8)
+-----------+

图 2:C 实现的alloca()的行为

Ruby 的missing/alloca.c就是这样一个模拟alloca()的实现。

现在让我们开始本章的主题,垃圾回收。

介绍 GC

[编辑 | 编辑源代码]

GC 做了什么

[编辑 | 编辑源代码]

标记 & 清除

[编辑 | 编辑源代码]

清除 & 复制

[编辑 | 编辑源代码]

引用计数

[编辑 | 编辑源代码]

对象管理

[编辑 | 编辑源代码]

struct RVALUE

[编辑 | 编辑源代码]

对象堆

[编辑 | 编辑源代码]

rb_newobj()

[编辑 | 编辑源代码]

rb_gc_mark()

[编辑 | 编辑源代码]

rb_gc_mark_children()

[编辑 | 编辑源代码]

寄存器

[编辑 | 编辑源代码]

mark_locations_array()

[编辑 | 编辑源代码]

is_pointer_to_heap()

[编辑 | 编辑源代码]

寄存器窗口

[编辑 | 编辑源代码]

机器栈

[编辑 | 编辑源代码]

Init_stack()

[编辑 | 编辑源代码]

STACK_END

[编辑 | 编辑源代码]

rb_gc_mark_locations()

[编辑 | 编辑源代码]

其他根对象

[编辑 | 编辑源代码]

特殊的 NODE 处理

[编辑 | 编辑源代码]

终结器

[编辑 | 编辑源代码]

rb_gc_force_recycle()

[编辑 | 编辑源代码]

注意事项

[编辑 | 编辑源代码]

内存释放

[编辑 | 编辑源代码]

GC 世代

[编辑 | 编辑源代码]

GC 中的 volatile 关键字

[编辑 | 编辑源代码]

初始化代码流

[编辑 | 编辑源代码]

gc.c 内部

[编辑 | 编辑源代码]

解释器内部

[编辑 | 编辑源代码]

对象创建

[编辑 | 编辑源代码]

分配框架

[编辑 | 编辑源代码]

用户定义的对象创建

[编辑 | 编辑源代码]

Data_Wrap_Struct()

[编辑 | 编辑源代码]

Data_Get_Struct()

[编辑 | 编辑源代码]

分配框架问题

[编辑 | 编辑源代码]

<hline>

评论、建议和批评可以发送到 Aoki MINERŌ <[email protected]>。 请将翻译评论、建议和批评发送到本章译者 mitcho (Michael Yoshitaka Erlewine) <[email protected]>。

华夏公益教科书