2.5 垃圾收集

Lua 采用了自动内存管理。这意味着你不用操心新创建的对象需要的内存如何分配出来,也不用考虑在对象不再被使用后怎样释放它们所占用的内存。Lua 运行了一个 垃圾收集器 来收集所有 死对象(即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、内部结构等,都服从自动管理。

Lua 实现了一个增量标记-扫描收集器。它使用这两个数字来控制垃圾收集循环:垃圾收集器间歇率垃圾收集器步进倍率。这两个数字都使用百分数为单位(例如:值 100 在内部表示 1 )。

垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。增大这个值会减少收集器的积极性。当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。设置这个值为 200 就会让收集器等到总内存使用量达到之前的两倍时才开始新的循环。

垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。不要把这个值设得小于 100 ,那样的话收集器就工作的太慢了以至于永远都干不完一个循环。默认值是 200 ,这表示收集器以内存分配的“两倍”速工作。

如果你把步进倍率设为一个非常大的数字(比你的程序可能用到的字节数还大 10% ),收集器的行为就像一个 stop-the-world 收集器。接着你若把间歇率设为 200 ,收集器的行为就和过去的 Lua 版本一样了:每次 Lua 使用的内存翻倍时,就做一次完整的收集。

你可以通过在 C 中调用 lua_gc或在 Lua 中调用 collectgarbage来改变这俩数字。这两个函数也可以用来直接控制收集器(例如停止它或重启它)。

2.5.1 垃圾收集元方法

你可以为表设定垃圾收集的元方法,对于完全用户数据(参见 §2.4),则需要使用 C API 。该元方法被称为 终结器。终结器允许你配合 Lua 的垃圾收集器做一些额外的资源管理工作(例如关闭文件、网络或数据库连接,或是释放一些你自己的内存)。

如果要让一个对象(表或用户数据)在收集过程中进入终结流程,你必须 标记 它需要触发终结器。当你为一个对象设置元表时,若此刻这张元表中用一个以字符串"gc" 为索引的域,那么就标记了这个对象需要触发终结器。注意:如果你给对象设置了一个没有 gc域的元表,之后才给元表加上这个域,那么这个对象是没有被标记成需要触发终结器的。然而,一旦对象被标记,你还是可以自由的改变其元表中的 __gc 域的。

当一个被标记的对象成为了垃圾后,垃圾收集器并不会立刻回收它。取而代之的是,Lua 会将其置入一个链表。在收集完成后,Lua 将遍历这个链表。Lua 会检查每个链表中的对象的 __gc元方法:如果是一个函数,那么就以对象为唯一参数调用它;否则直接忽略它。

在每次垃圾收集循环的最后阶段,本次循环中检测到的需要被回收之对象,其终结器的触发次序按当初给对象作需要触发终结器的标记之次序的逆序进行;这就是说,第一个被调用的终结器是程序中最后一个被标记的对象所携的那个。每个终结器的运行可能发生在执行常规代码过程中的任意一刻。

由于被回收的对象还需要被终结器使用,该对象(以及仅能通过它访问到的其它对象)一定会被 Lua 复活。通常,复活是短暂的,对象所属内存会在下一个垃圾收集循环释放。然后,若终结器又将对象保存去一些全局的地方(例如:放在一个全局变量里),这次复活就持续生效了。此外,如果在终结器中对一个正进入终结流程的对象再次做一次标记让它触发终结器,只要这个对象在下个循环中依旧不可达,它的终结函数还会再调用一次。无论是哪种情况,对象所属内存仅在垃圾收集循环中该对象不可达且没有被标记成需要触发终结器才会被释放。

当你关闭一个状态机(参见 lua_close),Lua 将调用所有被标记了需要触发终结器对象的终结过程,其次序为标记次序的逆序。在这个过程中,任何终结器再次标记对象的行为都不会生效。

2.5.2 弱表

弱表 指内部元素为 弱引用 的表。垃圾收集器会忽略掉弱引用。换句话说,如果一个对象只被弱引用引用到,垃圾收集器就会回收这个对象。

一张弱表可以有弱键或是弱值,也可以键值都是弱引用。含有弱值的表允许收集器回收它的值,但会阻止收集器回收它的键。若一张表的键值均为弱引用,那么收集器可以回收其中的任意键和值。任何情况下,只要键或值的任意一项被回收,相关联的键值对都会从表中移除。一张表的元表中的 mode 域控制着这张表的弱属性。当 mode 域是一个包含字符 'k'的字符串时,这张表的所有键皆为弱引用。当 __mode 域是一个包含字符 'v'的字符串时,这张表的所有值皆为弱引用。

属性为弱键强值的表也被称为 暂时表。对于一张暂时表,它的值是否可达仅取决于其对应键是否可达。特别注意,如果表内的一个键仅仅被其值所关联引用,这个键值对将被表内移除。

对一张表的弱属性的修改仅在下次收集循环才生效。尤其是当你把表由弱改强,Lua 还是有可能在修改生效前回收表内一些项目。

只有那些有显式构造过程的对象才会从弱表中移除。值,例如数字和轻量 C 函数,不受垃圾收集器管辖,因此不会从弱表中移除(除非它们的关联项被回收)。虽然字符串受垃圾回收器管辖,但它们没有显式的构造过程,所以也不会从弱表中移除。

弱表针对复活的对象(指那些正在走终结流程,仅能被终结器访问的对象)有着特殊的行为。弱值引用的对象,在运行它们的终结器前就被移除了,而弱键引用的对象则要等到终结器运行完毕后,到下次收集当对象真的被释放时才被移除。这个行为使得终结器运行时得以访问到由该对象在弱表中所关联的属性。

如果一张弱表在当次收集循环内的复活对象中,那么在下个循环前这张表有可能未被正确地清理。