Garbage collection and weak tables

Lua · Expert · 14 min

Long-running hosts (games, servers, editors) depend on predictable memory and leak-free caches.

Lua manages memory with a tracing garbage collector. Lua 5.4 offers two modes, switched with collectgarbage("incremental") or collectgarbage("generational").

  • Incremental (the library default) is a tri-colour mark-and-sweep that interleaves small steps with your program, so pauses stay short. The pause parameter controls how much memory grows before a new cycle starts; the step multiplier controls how much work each step does.
  • Generational collects young objects often and old ones rarely, which suits programs that create many short-lived tables. The standalone lua interpreter switches to this mode at startup.

collectgarbage("count") reports the heap in KB, collectgarbage() forces a full cycle and "step" does a bit of work.

A table with a metatable field __mode is weak: "k" makes keys weak, "v" values, "kv" both. A weak reference doesn't keep an object alive, so entries vanish when the object is otherwise unreachable. Use weak keys to attach data to objects you don't own and weak values for caches. Strings and numbers are values, not collectable objects, so they are never removed from weak tables. Ephemeron semantics mean a weak-key entry whose value refers back to its key is still collected.

__gc on a table's metatable registers a finalizer, called once before the object is collected. Finalizers run in reverse order of marking, may resurrect objects, and must be set when setmetatable is called. Prefer <close> for deterministic cleanup.

Example

local cache = setmetatable({}, {__mode = "k"})  -- weak keys

local obj = {}
cache[obj] = "metadata"
local n = 0
for _ in pairs(cache) do n = n + 1 end
print(n)                                 --> 1

obj = nil                                -- drop the only strong ref
collectgarbage()
n = 0
for _ in pairs(cache) do n = n + 1 end
print(n)                                 --> 0

local before = collectgarbage("count")
local junk = {}
for i = 1, 1e5 do junk[i] = {i} end
junk = nil
collectgarbage()
print(collectgarbage("count") < before + 100)  --> true

setmetatable({}, {__gc = function() print("finalized") end})
collectgarbage()                         --> finalized
print(collectgarbage("incremental"))     --> generational (previous mode, standalone lua)

Remember

  • Two collectors: incremental (library default) and generational
  • __mode = "k"/"v" makes weak tables for caches and side data
  • __gc finalizers run once before collection; prefer <close> for timely cleanup

Watch out for

  • Expecting string keys in a weak-key table to disappear; strings are not collectable references.

Check yourself

  1. You want a cache that doesn't keep its cached objects alive. Which metatable do you use?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.lua.org/manual/5.4/manual.html#2.5

Builds on

Unlocks

Nothing builds on this yet.