luaguides

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 on opt.

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.

  • arg is size (number). size == 0 runs one basic, indivisible step. A non-zero size runs a step that scales with that many kilobytes of work.
  • Returns true if the step finished a full cycle, otherwise false.
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.

See also