Skip to content

2.5.3 - GC元函数(Garbage-Collection Metamethods)

你可以直接对表、或通过C API对 full userdata 来设置GC元函数(参见2.4)。此元函数被称为终结器(finalizers),于垃圾收集器发现相应的表或 userdata 已死时被调用。终结器允许你将垃圾收集器与外部资源管理协调起来,例如关闭文件、网络或数据库连接,或者释放你自己的内存。

对于收集时要终结的对象(表或者 userdata ),你需要把它标记为可触发终结(finalization)。如果你想将标记一个对象,你需要设置一个对象的元表并且此元表要有元函数__gc。注意,当你设置元表时没有__gc属性,而是之后再于元表上创建这个属性的话,这个对象将不会被标记。

当一个被标记的对象死亡时,其并不会立刻被垃圾收集器收集起来。相反,Lua会将其放到一个列表中。Lua在收集完成后遍历这个列表。对于列表中的每个对象都会查找其是否有元函数__gc:如果有,Lua将这个对象作为单一参数来调用此函数。

在垃圾收集周期的最后,周期内被收集的对象中,终结器会以对象被标记为可触发终结的倒序来调用终结器;即,第一个调用的终结器是在程序中最后被标记为可触发终结的对象所关联的终结器。终结器的调用可能发生在常规代码执行时的任意时刻。

因为被回收的对象仍然会被终结器使用,所以其一定会被Lua复原(包括被其唯一关联的其他对象)。通常,这个复原是暂时的,而且其内存会在下一次GC周期内被释放。然而,如果终结器将对象保存到了某些全局位置(例如全局变量),那么这个复原就是持续的。此外,如果终结器将一个正在被终结的对象再次标记为可触发终结,那么终结器会在下个GC周期时的对象死亡的地方被再次调用。在任意情况下,没有被标记为可触发终结并已经死亡的对象,其内存只可能在GC周期内被释放。

当你关闭一个状态机(参见lua_close)时,Lua会调用所有被标记为可触发终结的对象的终结器,调用顺序与它们被标记的顺序相反。如果此步骤内的某些终结器再次标记了对象,那么这时的标记是无效的。

终结器不会让出或运行垃圾收集器。它们只能在不可预知的时间运行,所以在好的实现里会在必要的最低限度下正确释放与其关联的资源。

终结器运行时产生的错误会生成一个警告,此错误不会被传播。