Nanfeng

Notes on software development, code, and curious ideas

Understanding Garbage Collection in Lua

Lua automatically reclaims unreachable objects. Garbage collection prevents many manual-memory errors, but it cannot decide when logically obsolete references should be cleared or when an external resource must be released.

Reachability and weak tables

An object cannot be collected while a strong reference to it remains. Assign nil when a long-lived table no longer needs an entry. For registries and caches that should not keep objects alive, use a weak table:

1
2
3
4
5
6
7
8
9
10
local cache = setmetatable({}, { __mode = "v" }) -- weak values

local function intern(key, create)
local value = cache[key]
if value == nil then
value = create(key)
cache[key] = value
end
return value
end

__mode = "k" makes keys weak, "v" makes values weak, and "kv" makes both weak. When a weak key or value is collected, its whole entry is removed. Strings and numbers have special lifetime behavior, so test weak-cache assumptions with the actual object types involved.

Weak-key tables are also useful for attaching metadata to objects without extending their lifetime:

1
2
local properties = setmetatable({}, { __mode = "k" })
properties[someObject] = { selected = true }

Finalizers and external resources

Full userdata can define a __gc metamethod to release a native handle. Modern Lua versions also support table finalizers under specific rules. Finalization timing is nondeterministic, so provide an explicit close method for files, sockets, locks, and other scarce resources; treat __gc as a fallback.

Finalizers should be short, idempotent, and resilient to partially initialized objects. Avoid depending on the order in which related objects are finalized.

Controlling the collector

collectgarbage exposes diagnostics and tuning:

1
2
3
print(collectgarbage("count")) -- approximate KiB in use
collectgarbage("step", 100)
collectgarbage("collect")

Other options can stop and restart collection or tune its behavior, with details varying between Lua releases. Frequent full collections usually hurt latency and throughput. First remove accidental strong references and excessive allocation; tune the collector only after measuring a representative workload.

Garbage collection manages Lua memory, not application intent. Clear obsolete references, use weak tables for non-owning relationships, and close external resources deterministically.

+