collectgarbage
collectgarbage([opt [, arg]]) collectgarbage is the public interface to Lua’s garbage collector. It runs full cycles, drives incremental steps, switches between the incremental and generational modes, and reports how much memory Lua is holding. For background on how the collector decides what to free, see the Garbage Collection guide.
The function lives in the basic library, so it is available as a global in any standard Lua setup.
Signature
collectgarbage([opt [, arg]])
opt(string, optional) — operation selector. Default:"collect".arg(depends on opt, optional) — argument whose meaning depends onopt.
The return value depends on opt. With no arguments at all, collectgarbage() runs a full collection and returns true.
Options
collectgarbage("collect")
Performs a full garbage-collection cycle. This is the default when opt is omitted, so collectgarbage() and collectgarbage("collect") are equivalent. Returns true.
Use this when you want to free memory deterministically — for example, between two large allocations, or right before measuring a memory baseline.
print(collectgarbage("count")) --> e.g. 19.0546875
collectgarbage("collect")
print(collectgarbage("count")) --> smaller value
collectgarbage("count")
Returns the total memory in use by Lua, in kilobytes, as a number with a fractional part. Multiply by 1024 to get the exact number of bytes:
local kb = collectgarbage("count")
print(string.format("Lua is using %.0f bytes", kb * 1024))
A full cycle may defer finalizers to the next cycle in incremental or generational mode. If a finalizer hasn’t run after one call, run a second one:
while collectgarbage("count") > threshold do
collectgarbage("collect")
collectgarbage("collect") -- second pass to drain pending finalizers
end
collectgarbage("stop") and collectgarbage("restart")
"stop" disables the automatic collector. Manual calls ("collect", "step", and the rest) still work — only the automatic trigger is suspended. "restart" re-enables it.
print(collectgarbage("isrunning")) --> true
collectgarbage("stop")
print(collectgarbage("isrunning")) --> false
collectgarbage("restart")
print(collectgarbage("isrunning")) --> true
"stop" alone is rarely the right tool for latency-sensitive code. If you want bounded pauses in a hot loop, use "step" instead.
collectgarbage("step")
Runs one incremental step.
argissize(number).size == 0runs one basic, indivisible step. A non-zerosizeruns a step that scales with that many kilobytes of work.- Returns
trueif the step finished a full cycle, otherwisefalse.
for i = 1, 1000000 do
do_work(i)
if i % 1000 == 0 then
collectgarbage("step", 0) -- one basic step, bounded pause
end
end
This is the right shape for game loops, request handlers, and other latency-sensitive paths. A collectgarbage("collect") inside a hot loop is the most common source of GC-related hitches in Lua applications.
collectgarbage("isrunning")
Returns true if the automatic collector is currently enabled, otherwise false. This is the only way to query the collector’s state from Lua.
Tuning the collector
collectgarbage("incremental", [pause [, stepmul [, stepsize]]])
Switches the collector to incremental mode. All three arguments are optional numbers; pass 0 for any of them to leave that value unchanged.
pause— controls how long the collector waits between cycles, as a percentage of memory growth.stepmul— controls how much work the collector does per step, as a percentage of allocation rate.stepsize— basic step size in kilobytes.
Returns the previous mode as a string, either "generational" or "incremental".
-- Switch to incremental, keep step size unchanged
local prev_mode = collectgarbage("incremental", 200, 250, 0)
print(prev_mode) --> e.g. "generational"
collectgarbage("generational", [pause [, stepmul]])
Switches to generational mode. Lua 5.4+ only. Returns the previous mode. Default in Lua 5.4 is incremental, so a fresh state machine will report "incremental" as the previous mode on first switch:
local prev = collectgarbage("generational", 0, 0)
assert(prev == "incremental")
The legacy options "setpause" and "setstepmul" still work in 5.4, but the manual marks them as deprecated. New code should use "incremental" to set both values at once.
Finalizers and __gc
A full collection invokes finalizers (the __gc metamethod) on tables and full userdata that are about to be freed. Finalizers are most often used with full userdata coming from the C side — see the FFI guide and the weak tables guide for context on what survives until the next cycle.
local count = 0
local t = setmetatable({}, {__gc = function() count = count + 1 end})
t = nil
collectgarbage("collect")
collectgarbage("collect") -- a second pass may be required
print(count) --> 1
Whether one or two passes are needed is implementation-dependent, so the safe pattern is to keep collecting until a quiet state is reached.
Common mistakes
- Treating
collectgarbage("count")as an integer. It has a fractional part; multiply by 1024 for bytes. - Assuming
collectgarbage()is a no-op. It is"collect"by default and runs a full cycle. - Using
"stop"to “pause GC” in a hot path. It only disables the automatic trigger; use"step"for bounded work. - Calling
"collect"inside a tight loop. This produces long, unpredictable pauses. Step instead. - Reaching for
"setpause"or"setstepmul"in new code. They are deprecated in 5.4;"incremental"replaces both.