Up to now, i haven’t done anything with my variables after i’m done using them.
But i learned that this may cause memory leaks in certain situations, people have given me examples but i don’t understand. Can someone explain?
I’ve heard the terms “weak” and “strong” tables/variables but i don’t know what that means, i looked it up and it has something to do with metatables apparently, which made me more confused.
Lua is a garbage collected language, so you really don’t have to worry about memory leaks like you do in other lower level languages.
As long as you’re doing normal things you don’t have to worry, once you stop using a table it will be collected automatically once there are no more references to it.
Roblox instances however are something you have to worry about, if you create instances make sure to use :Destroy() when you’re done with them, since otherwise they will be a memory leak, since roblox maintains references to those internally.
Roblox’s luau has an implemented garbage collector, for which you don’t need to worry about. Java for example has that aswell (if you’re familiar).
In Roblox luau you don’t need to worry, like you would need to in C. In C for example, you use malloc, to allocate memory for certain variables and you have to close and clean everything yourself.
The only problem in studio, is when you create too much instances, that can cause lag.
You can help prevent lag, doing the right use of “Debris Service”.
local Function = function(Var)
while Var do -- Loop never stops
wait(1)
print(Var) -- Always prints Part
end
end
local Part = Instance.new("Part", workspace)
task.spawn(function()
Function(Part)
end)
wait(2)
Part:Destroy() -- Like i mentioned before, the loop keeps going and Part doesn't change
@Gamer539099
My only conclusion is that sometimes Debris Service and :Destroy() aren’t enough, the variable doesn’t update to nil after Destroying their Instance.
It’s due to var having a so called “Pointer” on the “Part”, so it just points towards the address of the part which doesn’t get deleted on runtime. I’m not 100% sure how roblox memory addressing works, but it’s most likely cleaned memory ports after you stop running the game.
A pointer essentially points on a object in this case. This object occupies a certain amount of memory and has a memory address which gets “freed” when you stop running the game, even tough you destroyed it.
As far as I understand it the GC will only free up the memory once no reference to it exists, but the function now holds a reference to the part, so the GC won’t collect it.
Destroy and debris set parents to nil and lock the properties, meaning you can’t create new references to the part, but any existing references outside the scope of Destroy won’t be removed.
I think in the example, attempting to find the part or access a property of it should then return nil (or error!?) which would then end the loop.
That is true, though they might still have internal references so to really clean them up for good you have to stop referencing them in code as well as call :Destroy()
Yes if you were talking about Luau’s cpp underside; however, in Lua you can just refer to it as a reference- because Lua(u) doesn’t have pointers and therefor it shouldn’t be referred to as such (Luau does have weak and strong references, which is much more idiomatic to Lua programmers than pointer may be).
Sigma is correct though. Here you run into a GC wall, garbage collection cannot know if you are finished or not with a destroyed object if you are maintaining reference to it in a separate thread.
The reference Var in the loop will persist the object as a strong reference. When the loop ends, and the incremental collector reaches its next marking stage, the object would then be marked for collection. As is when you destroy the object it will only become unparented (Roblox knows you’re done with it, but GC may not know until you discard your remaining reference). Basically, until the loop ends, the object will exist but entirely outside of the DataModel.
So after i destroy an instance, i have to make sure there is no variables being used that reference it? Is that it?
local Function = function(Var)
while Var.Parent do
wait(1)
print(Var)
end
-- Instance is destroyed, Part is locked and parentless, but still Var == Part
Var = nil
-- Part is destroyed, and no references to it, problem solved? does the GC service get rid of it now?
end
If it isn’t clear i’m having a lot of trouble figuring out when the GC service can collect my variables and when it isn’t able to.
Does it only happen because Var is being used in a loop?
Does it only happen because Var is an Instance?
Or does it only happen because Var is
An Instance
That happens to be in a While Loop
And the Loop happens to have a Var == true condition, meaning that the loop doesn’t end,
Because Destroying the Instance doesn’t set Var to nil, because the Var is being referenced in the loop itself !?!?! That’s a paradox
So the same thing will happen with this code when you maintain a reference.
local Part = Instance.new("Part")
local Reference = Part
Part:Destroy()
print(Reference)
You are maintaining a strong reference to a destroyed userdata object; and therefor the object persists outside of the DataModel because the Luau collector has no idea if you’re finished with it or not. Typically you won’t run into this issue because you shouldn’t need a million 'weak reference scenario’s in Luau. You mostly need one reference to one object. The exception comes here, where the extra strong reference will not go out of scope for the near future.
Doing the following on an Instance userdata object will absolutely guarantee it gets marked for collection:
Nullify the Cpp side [:Destroy()]
Ensure all strong references are either set to nil, or the remaining strong references are expected to go out of scope in reasonable time (this could mean the script, loop, or thread ends as well).
In the code above, Reference will deallocate, but only because the script ends, at which point non-Cpp strong references are deallocated.
Could you clarify the distinction between Strong and weak references? I’m not sure what you’re refering to. In your example are both Part and Reference strong? should i set both to nil?
When do variables go out of scope? I’m still unsure.
Other than this i think i understand what you’re saying.
Basically anything you can touch from your code, you can consider a strong reference, as the article I linked shows, you have to go out of your way to make weak references in lua.
Scope is in most cases just a block of code, it can be more complicated with closures but in most cases it will be straightforward.
-- Scope Begin
local a = {};
b = {}
if true then
-- Scope Begin
local c = {}
d = {}
-- Scope End
end
print(c) -- nil, c went out of scope since it was local to the if block's scope
-- c gets collected after next gc cycle since there are no more references to it in scope
print(d) -- table, without the local keyword variables are defined in global scope
a = nil
b = nil
d = nil
-- Scope End
-- The tables referenced by a, b, and d are no longer referenced by their respective variables and will be collected.
a and b are in the same scope, being local to global scope is still being in global scope.
The example is kind of bad since all of that would be collected whether or not they were set to nil after the script ends, but if for example you had a while loop right after d = nil
the collector could collect the tables even though the variables are still in scope, since the variables don’t reference the tables anymore.
You shouldn’t ever be able to actually observe GC setting anything to nil without using weak references (there are almost no reasons to ever touch those in lua)
To put it in simple terms, I think it’s pretty safe to assume that if you can access an object from your script, it can’t be garbage collected.
Once you set a variable containing a table to nil, and you don’t have a reference to it saved somewhere else, there is no way you will be able to get it back without making a brand new table, therefor it is safe for GC to go ahead and clean it up.
Yes, that’s exactly how it works. Things get collected once there are no more references left to it.
function create()
local tbl = {}
return tbl
end
local a = create()
while wait() do end
The tbl variable here does go out of scope, but the function returns the reference it contains, which gets saved in a, so it can’t be collected until a is set to nil.
if you just called create() without saving the result however it would still be collected.
Thank both of you very very very much for helping me with this one. Understanding that felt like trying to fit a square peg in a round hole.
Now this brings me back to the problem that made me find out about collection service in the first place: Variables that reference instances.
Unless i manually set them to nil, they will never be collected unless they are out of scope AND their instance is destroyed. Is this correct?
Read the comments in order?
local Function = function(Var)
while Var.Parent do -- 2nd: Loop keeps this variable from being collected
wait(1)
end
-- 5th: Var.Parent is nil, because the Part got destroyed, Var isn't nil tho, because it is inside this scope
end -- 6th: Scope ends, Var is nil?
local Part = Instance.new("Part", workspace) -- 1st: Variable references a Part
task.spawn(function()
Function(Part)
end)
-- 3rd: Scope ends, but Variable doesn't get collected because of the function
-- in another script
workspace.Part:Destroy() -- 4th: Part gets destroyed