Un-Referenced Object/Table Garbage Collection

Hi, got a question relating to memory and garbage collection.
So I’ve got a gun that fires bullets when the player clicks, and every time it does so, a new Projectile object is created. Note the Projectile.new()'s reference isn’t being held anywhere.
My question is, if the Projectile object isn’t being referenced anywhere after it’s creation, when will it be garbage collected? Should I store the new Projectile Object as a variable then set it to nil later? I’m worried about possible memory leakage.
I’ve looked at this post but I’m still unsure.

-- Within the Gun Object Class
function Gun:FireBullet()
	Projectile.new(self)
end
-- Within the Projectile Object Class
function Projectile.new(gun)
    local self = {}
    setmetatable(self, ProjectilePhysical)
	self.Bullet = gun.Ammo:Clone()
	-- Setting various self.Bullet properties omitted
	self.HitConnection = self.Bullet.Touched:Connect(function(hit)
		if hit.CanCollide then
			self.DealtDamage = self:OnHit(hit)
			self.HitConnection:Disconnect()
			self.HitConnection = nil
		end
	end)
	
	spawn(function() -- Spawning a function so return self doesn't have to wait
    	while self.HitConnection do -- Takes snapshot of Projectile's velocity for damage calculations
			self.VelocitySnap = self.Bullet.Velocity.magnitude
			wait()
		end
		wait(10)
		self.Bullet:Destroy()
	end)
	
    return self
end

Other than that if there are problems with my method please let me know

When an object has no more strong references to it, it will be eligible for garbage collection. Garbage collection on an object with no more references is not guaranteed immediately after the loss of the last reference, but an example of when it will be when the program deems to be running out of memory for example.

Also pls no oop

1 Like

No; assuming Projectile.new()'s call doesn’t internally store the new object anywhere, then soon after your function returns, its reference will be dropped. It can take an arbitrary amount of time for it to be collected, but you won’t have to worry about it sticking around.

Thanks!

@incapaxx Thank you for the information! And oop is life, can’t tell me otherwise.

What @Autterfly said is 100% correct. Once the function ends (so the context the variables are a part of is no longer in use) all of the variables no longer hold any reference to their values. This isn’t just true for functions, it’s true for any variable context. Here’s some examples:

local someValue = {} -- This holds a reference to a table

local function myFunc()
	print(someValue) -- We've taken someValue from the context outside. This function now also holds a reference to someValue. The more official name for this in lua is an "upvalue" (it's a value from an "upper" context)
end

local eventConnection = someRobloxEvent:Connect(myFunc) -- You can think of this as equivalent to inserting myFunc into a table. That table of connections holds a reference to myFunc, which holds a reference to someValue.

-- That is why it's important that you disconnect your events somewhere
eventConnection:Disconnect() -- You can think of this as removing myFunc from that "connections" table. Now that it's gone, it no longer holds a reference to myFunc.

-- We still have myFunc in this context though, but once our script completes myFunc will be completely unreferenced meaning 

do
	local myValue = {}
	
	myValue.SomeStuff = {1, "2", "three"}
	myValue.OtherStuff = {"a", "b", "c"}
	
	-- We're done with code in this block and it'll exit, taking its variables with it
end
-- Because "myValue" is within the above block, which has exited, it is no longer holding a reference here at all
local someOtherValue = {} -- This holds a reference, however it's at the end of the script (meaning once this script completes the same thing as the above do block will happen and the variable will no longer be in reference)

Some of this stuff gets a tiny bit fuzzy especially with having a whole new VM that we’re only recently learning about I suppose, but the good news is we don’t need to worry about the specifics, for example when GCs occur, we just need to know when to worry and when not to worry about things.

I’ve definitely learned more about GC since I wrote that post originally and I think that there are two main memory leaks that can occur:

  1. Inactive event connections
  2. Permanently referenced tables containing old data, almost entirely player instances in my own code (e.g. in a commonly used event, a module of some kind, etc)
  3. Permanently yielding code or loops holding references (this is a particularly nasty one imo because it’s easy to run into and it’s hard to know what Roblox has accounted for… This is my main use for do blocks and is one of my least understood ones simply because it’s not clear what behavior occurs. For example, if you do coroutine.yield do any variables before hold references if no code references them after? Does this behave the same as WaitForChild?)

I’m actually pretty glad you linked my post for this because this actually serves as an example of how my posts can be confusing :smile:… I often see people interacting with my posts but finding newer feedback is particularly hard because most people don’t want to necrobump things and sometimes people don’t want to say harsh things (myself included I suppose).

2 Likes

Omg thank you! But I totally didn’t mean your post that I linked was confusing at all, I just meant I was unsure how it related to my specific problem.
That do block thing is awesome too thanks!

1 Like