• 垃圾收集(garbage collection):自动地删除成为垃圾的对象
    • 只能收集确定是垃圾的内容
      • 如:驻留内存(hoarding memory)和外部资源不能自动回收
  • 辅助垃圾收集器的机制
    • 弱引用table(weak table):允许收集Lua中还可被程序访问的对象
      • 注:引用,类似指针,Lua中指指 table 或 函数
    • 析构器(finalizer):允许收集不在垃圾收集器直接控制下的外部对象
    • 函数collectgarbage:调用垃圾收集器使用
  • 垃圾收集器只能自动回收被认为是垃圾的数据

    • Lua中的垃圾:没有任何引用指向该对象,该对象就被认为是垃圾
    • 如:对栈进行操作;如果弹出一个元素,只对栈顶索引进行递减操作,那么对于垃圾处理器来说,该元素并不是垃圾;需要将对象所在位置赋nil值。

      弱引用table(weak table)

  • 弱引用table:该table中的引用不会阻止Lua的对象回收机制

    • 弱引用不在垃圾收集器考虑范围内的对象引用(即垃圾收集器不会考虑指向对象的弱引用)
      • 如:如果一个对象的所有引用都是弱引用,那么垃圾收集器会回收这个对象删除这些弱引用
    • 强引用:会阻止对象的回收,键和值都是强引用
    • 注:垃圾收集器会回收只有弱引用指向的对象
  • Lua通过弱引用table实现弱引用
    • 弱引用table:元素均为弱引用的table,如果对象只被弱引用table持有,那Lua会回收该对象
      • 弱引用table中,键和值都可以是弱引用的
  • 三种类型的弱引用table:

    1. 有 弱引用键 的table
    2. 有 弱引用值 的table
    3. 同时有 弱引用键 和 弱引用值 的table
    4. 注:只要有一个 键 或 值 被回收,那么整个键值对都会被回收

      创建弱引用table

  • table的元表__mode字段决定

    • __mode字段的值为字符串,对应如下:
      1. “k”:这个table的键是弱引用
      2. “v”:这个table的值是弱引用
      3. “kv”:这个table的键和值都是弱引用
    • 弱引用键:键为引用时,弱引用table中的一个如果为 table或函数 的引用,如果没有其他强引用指向该 table或函数 那么将会被回收
    • 弱引用值:值为引用时,弱引用table中的一个如果为 table或函数 的引用,如果没有其他强引用指向该 table或函数 那么将会被回收
    • 注:数值、Boolean、string不会被回收,除非键值中有一个被回收,才会被一起回收 ```lua —弱引用键table a = {} mt = {mode = “k”} —元表,设置mode setmetatable(a, mt) —给a设置元表,设置a为弱 引用键 的table key = {} —table对象 function fun() —函数对象 — body end

a[key] = 1 —将当前key对象的 引用 作为 键 a[fun] = 2 —将当前fun对象的 引用 作为 键 a[3] = 3 —数字型作为键

key = nil —清除key fun = nil —清除fun collectgarbage() —调用垃圾回收

for k, v in pairs(a) do print(k, v) end —[[ a是弱引用键table,所以a[key]和a[fun]为弱引用; 当key和fun对象清除时,没有其他的强引用指向原来的对象了, 所以弱引用table中a[key]和a[fun]中对应的对象会被垃圾收集器收集

输出: 3 3 —]]

—弱引用值table

a = {} mt = {mode = “v”} —元表,设置mode setmetatable(a, mt) —给a设置元表,设置a为弱引用值的table

key = {} —table对象 fun = function() —函数对象 end

a[1] = key —给a[1],当前key对象的引用 a[2] = fun —给a[2],当前fun对象的引用 a[3] = 3

key = nil —清除key fun = nil —清除fun

collectgarbage() —调用垃圾收集器 for k, v in pairs(a) do print(k, v) end —[[ a是弱引用值的table,所以a[1]为key对象、a[2]为fun对象,引用是弱引用; 当key和fun对象清除时,没有其他强引用指向原来的对象引用了, 所以只剩下弱引用a[1]和a[2]指向该对象,就会被垃圾收集器回收。

输出: 3 3 —]]

  1. <a name="QGGIN"></a>
  2. # 弱引用table的应用
  3. <a name="D0sZ8"></a>
  4. ### 记忆函数(Memorize Function)
  5. - 即使用 **空间换时间** :用**辅助table记忆**之前**执行结果**,然后**再次调用时**,从**辅助记忆table中调用**,从而加快处理速度。
  6. - 但是时间久之后,会浪费大量空间,因此将该**辅助记忆table设定为弱引用值table**,这样每个**垃圾回收周期**内,都会**回收**那个周期内**没有使用**的结果。
  7. ```lua
  8. --[[
  9. 如一个系统使用red、green和blue来表示颜色;使用一个函数,
  10. 每次调用时候,就使用该函数合成一个结果。
  11. --]]
  12. local result = {} --辅助记忆table
  13. setmetatable(result, {__mode = "v"}) --设置为弱引用值table
  14. function CreateColor(r, g, b) --创建颜色的函数
  15. local key = string.format("%d-%d-%d", r, g, b) --创建唯一的键
  16. local color = result[key] --获得记忆函数中键对应的值
  17. if color == nil then --如果为空
  18. color = {red = r, green = g, blue = b} --运行生成颜色的程序
  19. result[key] = color --将结果保存到记忆table中
  20. end
  21. return color
  22. end
  23. --[[
  24. 利用弱引用值的table保存处理rgb的生成结果,只要在一个垃圾回收周期内,
  25. rgb生成的颜色有被使用,记忆函数中对应的键值就不会删除,如果该颜色没有被使用
  26. 就会被回收,这样可以解决内存占用过多的问题。
  27. --]]

对象属性(Object Attribute)

  • 在面对对象中的 对偶表示 中,将对象的私有属性保存独立于对象的table中。
    • 如:对象为o,其含有一个私有属性balance;那么创建对象时,同时在一个名字为balance的table中创建一个键为o的索引,用来保存其私有属性;(balance[o]=0)
  • 如果使用该方法创建私有属性,就会出现:令o=nil后,因为table balance中仍然有对o使用,所以垃圾回收器不会回收o所占用的资源,导致系统资源的使用不充分。
  • 在该问题中,将table balance设置为弱引用键table,在o=nil之后,垃圾回收器就会回收对象,以释放空间

    具有默认值的table

    弱引用键实现

  • 用一个 弱引用table 来映射每一个 table 和它的默认值(将table引用作为索引) ```lua local default = {} setmetatable(default, {__mode = “k”}) —设置为 弱引用键 table

local mt = { —设置元表,设置元方法 __index = function(t, k) return default[t] —使用对象引用,作为索引 end }

—t为table,d为默认值 function setDefault(t, d) default[t] = d —将t的引用作为索引,设置在default table中 setmetatable(t, mt) —给t设置元表 end —[[ 在使用函数setDefault后,由于给table t设置了有元方法的元表, 在每次访问t时,如果该索引没有设置过值,都会返回默认的数据d。 并且如果table t释放,default为弱引用值table,t的资源也会被回收。 —]]

<a name="Vbqtz"></a>
#### 弱引用值实现

- 使用 **记忆函数** 的方式
```lua
local metas = {}
setmetatable(metas, {__mode = "v"})        --设置为 弱引用值 table

--t为table,d为默认值
function setDefault(t, d)        
    local mt = metas[d]            --从默认值d的 记忆 table中取得元表
    if mt == nil then                --没有该记忆
        mt = {                            --添加记忆
            __index = function()
                return d
            end
        }
        metas[d] = mt                --加入记忆
    end
    setmetatable(t, mt)            --设置元表
end
--[[
    该方法使用 记忆函数 ,实现带有默认值的table,
如果多个table有一样的默认值,在给table设置默认值时,就直接使用一个元表,可以节约空间;
如果一个默认值的所用table,都被释放,那么该默认值对应的元表也就被释放。
--]]

弱引用键实现 VS 弱引用值实现

  • 弱引用实现:每**table分配几字节**空间
  • 弱引用实现:给不同的默认值分配空间
  • 如果多条数据中:

    • 相同的默认值:使用 弱引用键 实现
    • 相同的默认值:使用 弱引用值 实现

      瞬表(Ephemeron Table)

  • 瞬表:具有 弱引用键 和 强引用值 的table

    • 即:table = {弱引用键 = 强引用值}
    • 顺表中的一个元素(k,v),指向k其它外部引用存在时候,指向v的引用就是强引用。 ```lua local mem = {} —辅助记忆 table setmetatable(mem, {__mode = “k”}) —设置辅助记忆table为 弱引用键的 table

function factory(o) —一个工厂函数 local res = mem[o] if not res then res = function() —创建一个闭包 return o end mem[o] = res —保存到辅助记忆table中 end return res —返回res结果 end —[[ 在程序运行中,如果调用了factory函数,如a=factory(o),那么会生成一个闭包 用记忆函数的方法将其记忆;如果再a释放之前再次调用,就可以直接冲记忆函数中获得, 避免再次创建闭包。 在记忆函数执行过程中,辅助记忆函数形成了一个 瞬表 ,在调用该函数时 o = {};a = factory(o);o=nil;虽然在mem中mem[o]中的键是弱引用键, 但是o在a中仍然有应用,并且a是mem[o]之外的一个引用,所以该引用是一个强引用, 不可回收。这样再次调用b = factory(o)时,就是从辅助记忆table中获得。 具体例子如下: —]] o = {} —对象 a = factory(o) —会将对象o的闭包保存在记忆函数中 o = nil collectgarbage() —不会回收mem[o],因为o被a引用

a=nil collectgarbage() —会回收mem[o],因为o此时只被mem[o]引用,没有其它外部引用存在


<a name="suDT3"></a>
# 析构器(Finalizer)

- **析构器**:与对象关联的**函数**,当对象即**将被回收时**该函数会被**调用**
<a name="ZCDlq"></a>
### 实现(元表:__gc)

- 通过设置**元方法__gc**实现(**输入参数**就是该 **析构对象**)
- Lua中特殊处:**将一个对象标记为需要析构**,给对象**设置**一个非空**__gc元方法**的元表,将其**标记为需要析构处理**
   - **标记析构(给元表设置__gc字段)必须在setmetatabele函数之前**
      - **可以先对__gc字段占位**
```lua
A = {x = "this is A"}
setmetatable(
    A,
    {
        __gc = function(o)
            print(o.x)
        end
    }
)
A = nil
collectgarbage()        --输出:this is A
--------------------------------------
A = {x = "this is A"}
mt = {}
setmetatable(A, mt)
mt.__gc=function (o)        --在给A设置元表之后设置__gc元方法,不会进行析构
    print(o.x)
end
A = nil
collectgarbage()                --不会对A进行析构
--------------------------------------
A = {x = "this is A"}
mt = {__gc = true}            --先对__gc字段占位
setmetatable(A, mt)
mt.__gc = function(o)        --后设置析构器
    print(o.x)
end
A = nil                
collectgarbage()                --输出:this is A

析构处理的顺序

  • 一个垃圾回收周期中析构多个对象,是按照被标记需要析构处理逆顺序
    • 设置__gc字段时,就会将其标记析构到析构栈中(入栈),进行析构时候从栈中拿出(出栈
    • 注:析构顺序与对象之间的联系无关(只与标记析构的顺序有关) ```lua A = {x = “this is A”} B = {x = “this is B”} C = {x = “this is C”} mt = { __gc = function(o) print(o.x) end }

setmetatable(B, mt) —标记析构 setmetatable(A, mt) —标记析构 setmetatable(C, mt) —标记析构,可看成加 入析构栈 —此时析构栈中:B A C A = nil B = nil C = nil
collectgarbage() —可看成从析构栈中 出栈 然后放到 析构 队列中,等待析构 —[ 输出: this is C this is A this is B —]

<a name="zDTnz"></a>
### 析构器的运行机制(复苏)

- **临时复苏**:在**析构器**被**调用时**,**参数**是正在被**析构的对象**,对象**会重新变得活跃**
   - 即:在上一段代码中,先使**A=nil**,在**析构时**,仍然可以访问的A中的x,因为此时**A**被**作为析构器**的**参数**,进行了**临时复苏**
- **永久复苏**:析构器执行期间,把**析构对象储存在全局变量**中,使得析构器结束之后,该析构对象仍可以访问
<a name="r3nvW"></a>
#### 回收所有垃圾

- 垃圾收集器的两个阶段:
   1. 垃圾收集器**首次发现**某个具有**析构器的对象不可到达**时,将其**复苏**,并**放入**等待被**析构队列**中;该**析构器开始执行**,该**对象**就会被**标记为已析构**
   1. 垃圾收集器**再次发现**该对象**不可到达**时,将该**对象删除**
- **注:要保证所有垃圾真的被释放,调用两次collectgarbage函数(第二次才会删除第一次调用的析构对象)**
<a name="XuyDf"></a>
### 实现atexit函数

- atexit函数:程序终止时,调用的函数
- 创建一个带有析构器的table,将其描定在某处(如全局table _G中)
```lua
local t = {
    __gc = function()
        --atexit函数
        print("finish program")
    end
}

setmetatable(t, t)
_G["*AA*"] = t            --加入到全局变量中

每次运行GC后运行函数

local mt = {
    __gc = function(o)
        print("new finalizer")
        setmetatable({}, getmetatable(o))        
            --在执行析构器中,创建一个带有析构器但是不被引用的table
    end
}
setmetatable({}, mt)

collectgarbage()        --输出:new finalizer
collectgarbage()        --输出:new finalizer
--结束时,会进行一次辣鸡回收,输出:new finalizer

弱引用table中的析构

  • 调用析构器之前 清理弱引用table的
  • 调用析构器之后 清理弱引用table的
  • 因此可以给 弱引用键table 设置析构

垃圾收集器 函数

垃圾收集器机制

  • Lua5.0及之前:简单的 标记-清除式(mark-and-sweep) 垃圾收集器(Garbage Collector,GC)
    • 又称为 stop-the-world(全局暂停)式收集器
    • 时不时停止主程序运行,来执行一次完整的垃圾收集周期(garbage collection cycle)
    • 每个垃圾收集器周期由四个阶段组成:标记(mark)、清理(cleaning)、清除(sweep)、析构(finalization)
      1. 标记(mark):将根节点集合(root set)标记为活跃,当所有可达对象都标记为活跃,标记阶段完成
        • 根节点结合:由可以直接访问的对象组成,Lua只包括C注册表
        • 保存在一个对象中的对象是程序可达的,也会被标记为活跃
      2. 清理(cleaning):处理析构器和弱引用table遍历所有被标记为需要进行析构、但又没有被标记为活跃状态对象将其标记为活跃(即复苏),放在一个单独的列表中(析构阶段使用);遍历弱引用table并从中移除键或值未被标记的元素。
      3. 清除(sweep):遍历所有对象(Lua将所有创建的对象放在一个链表中),没有被标记为活跃,将其回收否则清除标记,准备下一个清理周期
      4. 析构(finalization):调用清理阶段被分离的析构器
  • Lua5.1:使用 增量式垃圾收集器(incremental collector)
    • 步骤和老版的相同,但是 不需要在垃圾收集期间停止主程序 与解释器交替运行。
      • 交替方式(类似分时操作系统):解释器分配一定数量的内存,垃圾收集器就执行一小步
      • 为了防止在垃圾收集器工作时,解释器改变一个对象的可达性,因此加入了 内存屏障(barrier)
  • Lua5.2:引入 紧急垃圾收集(emergency collection)

    • 内存分配失败时,强制进行一次完整的垃圾收集;再次尝试分配,该情况发生在内存分配的任意时刻,因此该收集动作不能运行析构器

      collectgarbage控制步长

  • collectgarbage([opt,data]):对垃圾收集器进行控制

    • opt:可选字符串,控制函数进行那种操作
    • data:部分opt,需要使用data作为第二个参数 | opt对应的选项 | | | —- | —- | | “stop” | 停止垃圾收集器,直到使用选项”restart” | | “restart” | 重启垃圾收集器 | | “collect” | 执行一次垃圾收集器默认选项 | | “step” | 单步执行垃圾收集器,第二个参数data代表工作量,为0则收集器进行一步不为0,则收集器收集相当于Lua分配这么多(K字节)内存的工作。收集器结束这个循环则返回true → data:1024,让GC扫描1M数据 | | “count” | 以KB为单位返回当前使用的内存数,返回浮点数,乘以1024得到字节数。(包括未被回收的死对象) | | “setpause” | 设置收集器的pause参数(间歇率)。参数data为以百分比给出的新值(data为100时,参数设为1) → data:200,内存增大2倍(200/100)自动释放一次 | | “setstepmul” | 设置收集器的stepmul参数(步进倍率,step multiplier)。参数data也为新值,同为百分比单位。 → data:200,收集器单步收集的速度相对于内存分配的倍率为2倍(200/100) |

参数pause

  • 控制垃圾收集器在一次收集完成后等待多久再开始新的一次收集
  • data的值:

    • 0:在上次垃圾收集结束后立即开始新的一次垃圾收集
    • 200:表示200%,重启垃圾收集器前等待内存使用变为200%。消耗更多的CPU时间 换 更低的内存消耗,则设置的小一些,通常在0~200之间

      参数stepmul

  • 每分配1KB内存,垃圾收集器进行多少工作

  • data的值:
    • 越高,垃圾收集器使用的增量越小
    • 200:默认为200%,低于100%的值会让收集器运行很慢