Huge memory leak prevention for everyone (or most people atleast)

Introduction

Hello, I am making this tutorial since I used to be bad at optimization (and probably still am) and I want to make sure everyone is informed. but there’s this one detrimental cause for memory leaks that will be definitely occur if you have a popular game and I wanna make sure no one has to deal with it.

You might think you know about it if you’re a experienced scripter, but I told this information to scripters who have been scripting for 10x longer than I have (I’ve only been scripting for under one year) and even they didn’t know about this.

So what is this huge memory leak?

Basically, whenever you fire the player removing/character removing event. You’d expect the instance to have already been destroyed right? WRONG.

The engine does not automatically destroy player/character objects even after their respective events have fired, this means that things such as attributes being saved to a player. Events being connected to the player/character will continue to use up memory.

Why is this so detrimental?

As more users join and leave the experience memory usage will continue to grow, to the point that you get a huge memory leak.

This memory leak will be on the server.

Server memory leaks are particularly nasty because servers can run up for days or even weeks! Making it a bad experience for everyone and feeling like it’s everywhere.

Atleast with client memory leaks it’s only for the specific client and no one else is affected, and ends once the client leaves the game.

Why are memory leaks bad?

Memory usage is the amount of RAM or swap that your experience uses. Even if an experience has low starting memory usage, memory leaks can cause that amount to increase over time.

On the server, excessive memory usage can cause crashes, which disconnect all players from the experience.

Excessive memory usage causes client crashes, too, but it also prevents users on lower-end devices from playing your experience in the first place. Reducing memory usage can greatly expand your addressable audience, especially on mobile.

How do I fix this?

Simply destroy the player/character object on their respective removing events once you’re done using them.

Sample code:

Players.PlayerAdded:Connect(function(player)
  player.CharacterRemoving:Connect(function(character)
-- Pretend i'm doing other stuff before destroying it
    task.defer(character.Destroy, character)
  end)
end)

Players.PlayerRemoving:Connect(function(player)
-- Pretend i'm doing other stuff before destroying it
    task.defer(player.Destroy, player)
end)

Thank you to the post that gave the idea of using task.defer instead of just destroying, And make sure to wrap in pcalls.

Also make sure to repeat this until it’s successful:

Players.PlayerAdded:Connect(function(player)
  player.CharacterRemoving:Connect(function(character)
-- Pretend i'm doing other stuff before destroying it
   repeat
local success = pcall(function()
 task.defer(character.Destroy, player)
end)
task.wait()
until success
  end)
end)

Players.PlayerRemoving:Connect(function(player)
-- Pretend i'm doing other stuff before destroying it
repeat
local success = pcall(function()
 task.defer(player.Destroy, player)
end)
task.wait()
until success
end)
31 Likes

It does depend on how many connections or things like attributes you have saved to the player/character (or how popular your game is), so it could be less severe. But in general it would be a severe problem

Even if you had a game that only has connected one event to the player/character, since these are never disconnected after the player leaves/character is removed from workspace and users continue to join and leave it would also lead to a memory leak. Just slower. But most of you guys do more than just connecting one simple event.

4 Likes

I had no idea about this. Thank you so much for sharing your knowledge! Much appreciated. :smile:

5 Likes

No problem, I just wanted to make sure people knew about this before the problem arised for them.

6 Likes

This is actually really nice to know!
Should we do the same thing with the players character?
I was a little confused on that part.

Anyhow good stuff! Thanks!

3 Likes

Yes, sorry for not explaining that thoroughly. i will re-edit it later

Player.CharacterRemoving:Connect(function(Character)
-- Pretend I'm doing other stuff for the character before destroying it
Character:Destroy() -- Memory usage has now been free'd up!
end)
4 Likes

Workspace.PlayerCharacterDestroyBehavior, the default is just disabled right now.

2 Likes

Ah haven’t seen that, good catch!

However, still a few problems:

So I guess it still holds a need partially.

The documentation should also definitely update their page then.

Edit: After reading the update section a bit, some people have claimed that sometimes it wouldn’t even destroy the character automatically on the server:

Some people have also claimed this change has meant that ragdolls for characters are more of a hastle, so it may be preferred to set workspace.PlayerCharacterDestroyBehavour to a different behaviour and again only destroy the character/player when you are done with it.

3 Likes

This should be in #bug-reports:engine-bugs

1 Like

It’s not a bug and is completely intentional by roblox.

They want you to keep the character/player object even after leaving the game because you may want to use it on their removing events. But destroy them manually once you have used them for your needs.

Problem is, many people did not know roblox doesn’t destroy it automatically.

Roblox’s documentation quote on quote says:

“The engine does not automatically destroy player and character objects”

1 Like

I didn’t even think about replication!

Although, you’re fix may break if someone uses Immediate SignalBehavior or Roblox changes the default for deletions. You’d want to defer and wrap those calls.

local Players = game:GetService("Players")

local function Destroy(Object: Instance)
	task.defer(pcall, game.Destroy, Object)
end

local function OnPlayerAdded(Player: Player)
	Player.CharacterRemoving:Connect(Destroy)
end

Players.PlayerAdded:Connect(OnPlayerAdded)
Players.PlayerRemoving:Connect(Destroy)

I’d also mirror this on the client if destroying isn’t replicating.

3 Likes

Yes this is actually similiar to a code sample from the documentation.

Great idea.

1 Like

Thank you to @index_self for mentioning the announcement update given by roblox.

Currently, from what I’ve gathered from research on the announcement, many people are experiencing issues with the new implementation.

So it would be best to disable both player/character destroy behaviour on the workspace and set up this manual destroying yourself.

This may change later on as roblox updates, but right now it’s not 100% safe to use the default property.

2 Likes

I got the part where I have to manually destroy the character for it to be cleared from memory, but do I need to destroy the Player instance aswell?

2 Likes

Yes, both are not automatically destroyed by the engine.

2 Likes

I’ve updated the tutorial a bit, I suggest giving it a re-read for the best approach.

2 Likes

Don’t these instances simply get garbage collected once all references to them have closed?

In the example bellow, the memory usage goes up very fast, but ends up stabilizing. Just setting the Parent to nil, and ensuring there is no reference to the part, is enough for it to be cleaned up

while true do
	for i = 1, 100 do
		local Part = Instance.new("Part")
		Part.Parent = script
		
		task.delay(1,function() 
			Part.Parent = nil
		end)
	end	
	task.wait()
end

If you remove the task.delay function, the memory never stops going up

It’s not good practice to just parent things to nil instead of destroying them, but it does not necessarily cause memory leaks

1 Like

Its a bug because they could automatically remove it after the functions are called. there is no player(client) so there is nothing to do with it, maybe only with the physical character but not the player

1 Like

This is true for regular objects such as baseparts, but the engine specifically states that they do not garbage collect player/character objects:

Hmm, possibly. But I do not have access to the bug reports unfortunately.