Know if something is garbage collected?

Is there a way to actually know if something got garbage collected?

Example:

local p = Instance.new("Part")

local connection = p.Changed:Connect(function(property: string)
	print(property)
end)

task.wait(1)
p:Destroy()

Would connection and p get garbage collected here? There is no easy way to check from what I can tell, since printing either of them would still work since it would need to keep a reference to the variable.

Printing gcinfo() isnt all that useful either since we dont know if the count increases because of this script, or if its just doing anything else in the background.

Are you trying to check if a part gets destroyed?

There’s an event for that.

local part = workspace.Part

part.Destroying:Connect(print)

task.wait(10)

part:Destroy()

Nope. Im trying to check if the connection would cause a memory leak here, since its kept in a variable.

I’m not familiar with memory leaks, but I guess if this is in a server script, it’s kept private.

This might help:

connection:Disconnect() --to remove the connection
connection = nil --to remove the reference

In your example, this would cause a memory leak as a reference to p is maintained in the connection, and the connection itself still has a reference in the code.
Destroy() doesn’t find all the possible references to a part, it simply parents it to nil and locks the property. It might appear absent in the workspace, but if other parts of code reference it then the GarbageCollector won’t actually collect it.

TBH I’m not sure how to definitively check a specific reference has been GC’d, other than to ensure all connections are disconnected and you set references to nil.

That post unfortunately doesnt help much.

The reason why I made my own post in the first place is because I have something like this to handle character deaths and respawns:

local deathListeners = {}

local events = {}


type DeathListener = {
	Died: RBXScriptSignal,
	Respawned: RBXScriptSignal,
}


function connectDeath(player: Player, character: Model): ()
	character:WaitForChild("Humanoid").Died:Once(function()
		events[player].DiedEvent:Fire()

		player.CharacterAdded:Wait()

		events[player].RespawnEvent:Fire()
	end)
end


function deathListeners.new(player: Player): DeathListener
	if not events[player] then -- create new events
		local diedEvent = Instance.new("BindableEvent")
		local respawnEvent = Instance.new("BindableEvent")

		if player.Character then -- handle existing character
			connectDeath(player, player.Character)
		end

		player.CharacterAdded:Connect(function(character: Model) -- new character added
			connectDeath(player, character)
		end)

		player.AncestryChanged:Once(function() -- player left the game
			diedEvent:Destroy()
			respawnEvent:Destroy()

			events[player] = nil
		end)

		events[player] = {
			DiedEvent = diedEvent,
			RespawnEvent = respawnEvent,
		}
	end

	local event = events[player]

	return {
		Died = event.DiedEvent.Event,
		Respawned = event.RespawnEvent.Event,
	}
end


return deathListeners

…and

-- in other server script
local deathListener = deathListeners.new(player)

deathListener.Respawned:Connect(function()
	-- do whatever
end)

Wouldnt the script above cause a memory leak? I am destroying the events in the module, but they probably wouldnt get garbage collected since the server script has a reference to them, right?

Put deathListener.Respawned:Disconnect() inside the deathListener.Respawned:Connect() event.

Edit: I just realized the line where deathListener.Respawned:Connect() is at didn’t returned as a connection, make sure to make it as a variable and do respawnedConnection:Disconnnect() instead.

Unfortunately that connection needs to fire multiple times. Thats why I made a separate module for handling deaths/respawns in the first place.

IE: I only need 1 connection to handle EVERY death of a certain player, instead of having to connect to each new humanoid separately.

I misread the bottom part of your problem, so basically,

-- in other server script
local deathListener = deathListeners.new(player)

deathListener.Respawned:Connect(function()
	-- do whatever
end)

No it wouldn’t cause any memory leak since you’re destroying both events and unindexing events[player]. If you’re like me I would’ve also put table.clear(events[player] too because I’m paranoid.

1 Like

To know whether something was garbage collected, you can use a WeakReference. WeakReferences reference an object but don’t prevent it from being garbage collected. In Lua, you can create WeakReferenced tables like so:

local tbl = {}
setmetatable(tbl, {__mode = "v"})

An example:

local function tprint(tbl)
  for k, v in pairs(tbl) do
    print(tostring(k).." "..tostring(v))
  end
end

local tbl = {"a", 2, 3.5, {}}
tprint(tbl)

print()

setmetatable(tbl, {__mode = "v"})
collectgarbage()
tprint(tbl)

Output:

1 a
2 2
3 3.5
4 table: 0x55c4e208bfd0

1 a
2 2
3 3.5

The reference type (the table at index 4) was garbage collected.

So, if you want to know whether connection was garbage collected:

local weak_references = {}
setmetatable(weak_references, {__mode = "v"})

weak_references[1] = connection

-- ...

collectgarbage()
print(weak_references[1])

But remember that if connection is still accessible (i.e. it is still in scope) it will not be garbage collected, even if it is not used again.

2 Likes