Dispose things correctly in three.js

performance
dispose

#1

Hi!
I know this has been already asked several times, but I can’t get rid of memory leaks in a reactive app when using threejs. Investigating the problem, I found that I am not even able to correctly dispose a scene (without rendering).
Let me show you:

https://plnkr.co/edit/Z0sXOnXYc2XOV4t9tETv

In the above example, initially 3 THREE objects are instantiated (as you can see there is no rendering going on, just instantiating the objects):

  • scene
  • camera
  • renderer

Using the chrome devtools let’s take a memory snapshot just after loading the page:

Now let’s click on the “ADD 1000 MESHES” button, which as you can guess simply create 1000 meshes (BoxGeometry + MeshBasicMaterial) and add them to the scene object.
Let’s take another memory snapshot and see the comparison (delta) with the previous snapshot:

As you can see we passed from 25.2 Mb to 36.2 Mb and there are +1000 Mesh objects added in memory.

Now clicking the “DISPOSE” button we’ll trigger the following dispose function:

   const dispose = (e) => {			

		// dispose geometries and materials in scene
		sceneTraverse(scene, o => {

            if (o.geometry) {
                o.geometry.dispose()
                console.log("dispose geometry ", o.geometry)                        
            }

            if (o.material) {
                if (o.material.length) {
                    for (let i = 0; i < o.material.length; ++i) {
                        o.material[i].dispose()
                        console.log("dispose material ", o.material[i])                                
                    }
                }
                else {
                    o.material.dispose()
                    console.log("dispose material ", o.material)                            
                }
            }
        })			

        scene = null
        camera = null
        renderer && renderer.renderLists.dispose()
        renderer = null

        addBtn.removeEventListener("click", addMeshes)
        disposeBtn.removeEventListener("click", dispose)

        console.log("Dispose!")
	}

In this function we traverse the scene and dispose every geometry and material. Then we set to null the references to scene, camera and renderer and finally we remove the listeners to avoid memory leaks.
Let’s click on DISPOSE button and take another memory snapshot. I expected that the garbage collector will completely remove from memory all the data related to the 1000 Meshes (Mesh, Matrix4, Vector3, BoxGeometry, etc…) but if we take another memory snapshot we’ll find something very different:

It seems that 1000 Mesh objects have been deleted, but the memory usage is almost the same as in the previous snapshot (34.6 vs 36.2 Mb). There are some drops in the Vector3, Matrix4, Quaternion and Euler objects, but most of the objects keep to be persisted in memory and are not collected from the garbage collector. Indeed, if we compare the snapshot 3 to the snapshot 1 we find exactly that:

Please could someone explain what’s going on and how correctly dispose things in three.js?

Three.js: 102
Google Chrome: 72.0.3626.121 (64-bit)


#2

Yes, this is still a problem in the current three.js version (R102) since the internal render lists still hold references. There is already a PR to solve the issue but unfortunately is was not merged yet:

You should be able to mitigate the memory leak by doing this in your dispose method:

renderer.renderLists.dispose();

It would be great if you can also vote at github for a change :innocent:


#3

/cc


#4

Hi @Mugen87, the problem here is that even when calling renderer.renderList.dispose(), most of the objects are kept in memory (Vector3, Vector2, Color etc…).

See this other plunker and try to take a memory snapshot before adding meshes, after adding meshes and after dispose. You will see that even if the Meshe objects are disposed, most of the memory is still occupied as shown in the screenshots

So from what I am seeing renderer.renderList.dispose() is not enough to clear the memory correctly.

ps: I will glad to vore for the github request


#5

What happens if you call renderer.dispose()?


#6

The same, most of the objects are kept in memory.

This is the comparison between snapshot3 (disposed) and snapshot2 (meshes added):

This is the comparison between snapshot3 (disposed) and snapshot1 (no meshes added):


#8

How long is the time gap between both snapshots?


#9

Some seconds, but I force garbage collection before taking each snapshot


#10

I have locally reproduced the issue but I don’t know so far why these objects are not disposed. Even executing renderer.dispose() and setting all app variables to null does not seem to free the memory.

However, objects are created and retained as soon as three.js is loaded (e.g. closure scoped variables). So the library might cache objects at places we are not aware of yet.


#11

Yes this is a major problem, especially in reactive applications. For example if you switch back and forth between different views, one with threejs and one without, even if you dispose and clean everything as we did the memory is going to grow at each step because of the threejs objects kept in memory every time, potentially crashing the application at some point.

Do you think I should drop a bug issue abot this on github?


#12

That might be the best. Apart from that, I wonder if it’s possible with Chrome dev tools to find out at what places in the code references are hold to objects :thinking:


#13

I don’t know, I am currently learning about devtools, if I find something I’ll let you know.

Btw bug issue filled: https://github.com/mrdoob/three.js/issues/15972


#14

I’ve noticed something interesting. When I remove all console loggings from your code, I’m unable to reproduce the issue anymore.

image

It seems that logging geometries, materials etc. leads to a preservation of the respective objects. We had the same issue once at github which caused quite some confusion :wink:

Please remove all console.log statements and try it again.


#15

Wow that’s incredible, I can confirm that without the console.log all the objects seems correctly disposed!

Cheeers :beers:


#16

I’m also running into memory leak problems with ThreeJS, eventhough I’m calling dispose() on all materials and geometries…
Why aren’t these just disposed when meshes are removed from the scene?

Also: Isn’t there any way to completely dispose a scene when not in use any longer?


#17

Taking a snapshot of my memory looks like this:

I’ve tried disposing all I can, and I don’t have any console.log of any objects that can be disposed… I’ve also tried renderer.dispose() and renderer.renderLists.dispose(), but I just can’t get rid of the memory leak on Chrome using React…


#18

Simply removing meshes from the scene won’t dispose them. There are many use-cases where you need to remove a mesh temporarily and then add it back later, which would be impossible if the object no longer exists.

Take a look at @revyTH’s plnkr link to see how he did it: https://plnkr.co/edit/pmeB0t3tEbOAaKdebhbr

Are you removing references to your objects with something like myExampleCube = null; ? Hard to tell what the problem is without being able to look at your code.


#19

There are many use-cases where you need to remove a mesh temporarily and then add it back later, which would be impossible if the object no longer exists.

True, this makes sense. I just thought when implementing that when the mesh is no longer in the scene, and I don’t have a reference to it, it would be disposed automatically by the GC as well as the geometries, materials and textures used for it.
So when finding out now, that I have to call .dispose() on all my geometries, materials and textures after removing the mesh from the scene is kinda like a “gotcha!”.

I’ve spent some time trying to implement this but without much luck. The application also became much slower after having to call .dispose() on every geometry, material and texture I create… But it didn’t remove my memory leak, so it seems I keep some references around somewhere else. I believe its more due to me not managing my references properly because I just learned the frameworks I’m using… :expressionless: But at least I know where the memory leak is, so I can try to fix it in the future.

Can you confirm that after creating a mesh with a geometry, texture and a material, that in order to clean up, I need to:

  1. Remove the mesh from the scene, and remove the reference to it.
  2. Call .dispose() on geometries, textures and materials used, and remove the references to them.

It seems unnecessary to me in @revyTH’s example, that he needs to call dispose on child on each child object as well?

I can also see that the way I’ve been using ThreeJS, I haven’t reused much else but loaders, which is my own mistake for not knowing I guess. It seems I could have reused textures, geometries and materials instead of creating them each time I need a mesh… Whoops.


#20

Reusing materials and textures greatly seems to reduce the memory footprint as well.

Eg. in @revyTH’s code, doing:

function addMeshes (e) {
    let boxGeometry = new THREE.BoxGeometry(1, 1, 1);
    let material = new THREE.MeshBasicMaterial({color: 0xff0000});
    for (let i = 0; i < 100; ++i) {
		let mesh = new THREE.Mesh(
			boxGeometry,
			material)
		mesh.position.set(Math.random() * 100, 0.5, Math.random() * 100)
		scene.add(mesh)		
    }
    boxGeometry.dispose();
    material.dispose();
}

instead of:

function addMeshes (e) {		
	for (let i = 0; i < 100; ++i) {
		let mesh = new THREE.Mesh(
			new THREE.BoxGeometry(1, 1, 1),
			new THREE.MeshBasicMaterial({color: 0xff0000}))
		mesh.position.set(Math.random() * 100, 0.5, Math.random() * 100)
		scene.add(mesh)				
	}			
}		

Also seem to reduce the memory footprint.


#21

Yes of course, it was just for the example to show an evident increase in memory usage :wink: