Destroying objects increases untracked memory

Greetings,

I noticed that servers with a long lifespan tended to have high untracked server memory, and I would like to know how to either free this memory or keep it from getting that high in the first place.

After some experimentation, I found that loading in player’s characters and then destroying them increases server memory.

Sample: 4000 unique user’s characters loaded in over the course of 10 minutes (400 requests per minute from the API). Once the 10 minutes are finished, all models are removed using :Destroy() or other methods.

Environment: Not in studio. Testing in studio did not give accurate memory readings in Play Solo or Local Server.

Results: During the 10 minutes, untracked memory remains relatively low (100-200). Once all models were destroyed, untracked memory shot up past 1700. Staying in the game several minutes after destroying the models did not decrease untracked memory.

4000charactermemory

I tried a few destroying solutions to see if they help:

  1. :Destroy()
  2. Parent = nil
  3. Depth first removal of descendants
  4. Debris:AddItem
  5. Removing Humanoid first and then the model

Here is the server script I used to achieve this:
Note: There is a dummy character model inside the script running. Its the standard R15 Dummy generated from the animator plugin.

task.wait(8)

local api_request_limit = 400 -- api request limit is 400 a minute, so this takes X minutes to load
local userload = api_request_limit * 10

local Players = game:GetService("Players")
local Debris = game:GetService("Debris")

local function loadid(id) -- given userid, put character randomly in workspace
	local dummy = script.Dummy
	local success, errmsg = pcall(function()
		local character:Instance = dummy:Clone()
		local des = Players:GetHumanoidDescriptionFromUserId(id)
		character.Name = ""
		character.PrimaryPart:PivotTo(CFrame.new(math.random(-500, 500), 0, math.random(-500, 500))*CFrame.Angles(0,math.random()*math.pi*2,0))
		character.Parent = workspace
		character.Humanoid:ApplyDescription(des)
	end)
	if not success then warn(errmsg) end
end

local function recursive(obj) -- NOT IN USE -- a method to see if this makes it clear memory easier -- 
	for _,v in pairs(obj:GetChildren()) do
		recursive(v)
	end
	pcall(function()
		obj.Parent = nil
		--obj:Destroy()
		--Debris:AddItem(obj)
	end)
end

do
	
	local start = DateTime.now()
	
	local n = userload/api_request_limit
	for j = 1, n do
		for i = 1, api_request_limit do
			loadid(3652405999 + (j-1)*api_request_limit + i)
			task.wait()
		end
		print(string.format("set %d complete", j))
		task.wait((start.UnixTimestamp+60)-DateTime.now().UnixTimestamp) -- if we load <api_request_limit> users in under a minute, wait until the minute passed before continuing --
		start = DateTime.now()
	end

	task.wait(5)

	for _, v in pairs(workspace:GetChildren()) do
		if v:IsA("Model") and not Players:GetPlayerFromCharacter(v) then
			--local hum = v:FindFirstChild("Humanoid")
			--if hum then hum:Destroy() end
			--task.delay(.1,function()
			--	recursive(v)
			--	--v.Parent = nil
			--	v:Destroy()
			--end)
			--v.Parent = nil
			--Debris:AddItem(v)
			--recursive(v)
			v:Destroy()
		end
	end
end

I have looked through other devforum posts similar to this and still haven’t found a solid solution to avoid this memory increase. I kept the experiment as minimal as possible (no tables or connections) to try to find a solution, but any insight would be appreciated.

Devforum links:
Garbage collection
Weak tables
Caching parts for mining game
Connections causing memory leaks

4 Likes

This topic may seem like an obvious conclusion, but does anyone have any insight as to either:

  1. Avoid the problem from happening in the first place
  2. Force untracked memory to go down somehow through some black magic

Any information on this would be helpful

After some tinkering, I realized that my experiment is unrealistic as it expected 4000 players to be in the game at one time.

I cycled the creation and removal of the models 40 at a time, every 10 seconds (per API limit), and tracked how many unique assets were loaded through the humanoid description (excluding animations).

image

I loaded 10,000 users over 42 minutes and realized that since most of them were a default character, a lot of the assets were probably recycled after they were destroyed.

The final untracked memory was 174 mb.

My next question then is "Does the number of destroyed unique items scale with untracked memory?"

I’m having a similar issue:

How about using debris

local debris=game:GetService("Debris")

local part=Instance.new("Part")
part.Parent=workspace
task.wait(5)

debris:AddItem(part,0)
--I could have made the 0 a 5 in this case
--you must always have atleast a 0

Virtually, aren’t they the same thing?

I really don’t know other than debris is made to help the system work better. I take it that means some type of memory management is involved. Either way this is very easy to use and just the fact you’re using it changes how you set things up creating a better managed system.