Engineering
Finding and fixing memory leaks in FiveM resources
Track down memory growth in FiveM Lua resources: the usual causes (per-player tables never cleaned, handlers registered repeatedly, entities never deleted, growing caches), measuring with resmon and collectgarbage('count'), cleanup on playerDropped and onResourceStop, and caching with limits.
Overview
A resource that uses 2 MB after a restart and 200 MB six hours later is leaking. Lua has a garbage collector, so leaks in FiveM scripts are almost never lost memory — they are data your code keeps a reference to and never lets go: a table keyed by player that nobody cleans, a cache without a limit, an event handler added again on every call.
Measuring
CreateThread(function()
while true do
Wait(300000)
print(('[%s] Lua memory: %.1f MB'):format(GetCurrentResourceName(), collectgarbage('count') / 1024))
end
end)Short spikes are normal — the collector runs in steps. A line that keeps rising across hours is a leak. Resmon shows per-resource numbers on the client — see resmon explained.
The usual causes
| Cause | Fix |
|---|---|
data[source] = ... never removed | Clear it in playerDropped |
AddEventHandler inside another handler | Register once at file level |
CreateThread per action that never ends | Let threads exit, or reuse one loop |
| Caches that only grow | Size limit or time-based expiry |
| Entities, blips, zones never removed | Delete them when done and on resource stop |
| Big strings built repeatedly | Build once; use table.concat |
Cleanup patterns
local sessions = {}
AddEventHandler('playerDropped', function()
sessions[source] = nil
end)
local cache, order, MAX = {}, {}, 500
local function remember(key, value)
if not cache[key] then
order[#order + 1] = key
if #order > MAX then cache[table.remove(order, 1)] = nil end
end
cache[key] = value
end
AddEventHandler('onResourceStop', function(res)
if res ~= GetCurrentResourceName() then return end
-- delete entities, blips and zones created by this resource
end)Handlers registered inside loops are also a correctness bug: each one fires, so events run several times. Event basics: events explained.
Client-side leaks
- Props and peds spawned for effects and never deleted.
- NUI elements created on every message and never removed.
- Streamed textures requested repeatedly without releasing them.
- ox_lib points and zones created on every enter instead of once.
Frequently asked questions
How do I check a FiveM resource’s memory use?
Use resmon on the client or print collectgarbage('count') (in KB) from the resource over time.
Does Lua garbage collection prevent leaks?
It frees unreferenced data. Leaks happen when your code keeps references, such as tables that are never cleaned.
What is the most common leak in FiveM scripts?
Per-player data stored by source and never cleared when the player leaves.
Will a server restart fix leaks?
It resets memory, but the leak returns. Fix the reference that keeps growing.
Need this built, not just explained?
Ten years of FiveM work, from Lua to NUI
Custom resources, React NUI, ESX / QBCore / Qbox integration, OneSync performance audits and security reviews — plus the websites and SEO around your server brand.
Related guides
- EngineeringOptimising FiveM Lua scriptsSleep loops dynamically (long Wait when nothing is nearby, Wait(0) only when you must draw or read input), replace polling with events and state bags, measure distance with #(a - b) on vectors, read values like the player ped once per tick (or from ox_lib’s cache), use backtick hashes, avoid creating tables and strings in hot loops, and batch database writes. Measure before and after with resmon and the profiler.
- Engineeringresmon: finding the resource that is eating your framesPress F8 and type resmon 1 (or resmon true) to open the client resource monitor. The CPU msec column is how many milliseconds per frame each resource uses; idle resources should sit near 0.00–0.02 ms, and anything consistently above about 0.5 ms deserves a look. Sort by CPU, reproduce the situation where it lags, and inspect the worst resource’s loops.
- EngineeringUsing the FiveM profiler to find slow codeRun profiler record 500 in the server console (or F8 on the client) to record about 500 frames, check progress with profiler status, then run profiler view to open the result in Chrome, or profiler saveJSON name.json to save it. In Chrome DevTools’ Performance tab, look for tall frames and hover the coloured blocks to see the resource, file and line.
- EngineeringThreads and Wait in FiveM: loops that do not eat framesCreateThread(fn) starts a coroutine that runs alongside the game; Wait(ms) pauses it and lets everything else run. Wait(0) resumes on the next frame, so the loop runs every frame (60+ times a second). Use it only while you must draw or read input every frame; otherwise sleep for hundreds of milliseconds, and make loops adaptive — fast when the player is near something, slow when they are not.