Does threejs leak memory?


I use Threejs to make some fairly simply scenes, as shown, depicting a well. But I will display well after well after well, and that seems to cause the computer UI and the UI of any open programs to bog down. That makes me suspect that Threejs leaks memory, or that my code is causing the leak. Every time I create a mesh out of a geometry and a material, I then dispose of the geometry and material. Do I also need to set scene = null or some such thing after closing one well view and before opening another?

  1. Go to any of the official examples of three.js and repeat what is causing memory leak on your machine. Is the memory leak still there? Then it’s three.js, and it’s best to open an issue on the topic.

  2. Is the memory leak not happening in the official examples? Then the memory leak is most likely caused by your code.

1 Like

Not sure I follow @mjurczyk. But consider this:
My application will display a Threejs rendering of a drainpipe.

The user clicks a button to open a Bootstrap modal, which causes the scene to render in the modal.

From the modal (id is MyModal)…


<div id="DrainPipeHolder" style="padding:0px;min-width:0px; border-radius: 0rem; position:relative">
</div>

From my js module…

$('#MyModal').on('hidden.bs.modal', function (e) {
	try {
		group = null;
		controls = null;
		renderer = null;
		camera = null;
		scene = null;
	}
	catch (err) {
		console.log('Modal close: ' + err.message);
	}
	try {
		$('#DrainPipeHolder').empty();
	}
	catch (err) {
		console.log('Modal close: ' + err.message);
	}
});

renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.domElement.id = 'DrainPipeCanvas';
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
let container = document.getElementById('DrainPipeHolder');
container.appendChild(renderer.domElement);

As you can see, I try to nullify/destroy everything when the modal closes. Nonetheless, if a user opens and closes the modal repeatedly, the responsiveness of not only the scene but the entire browser becomes sluggish.

After closing the browser, Task Manager reveals that there is still browser activity.

I’m not sure that assigning a null value will dispose resources properly. It will only remove the JavaScript object, but will not remove GPU and other allocated resources.

More info: How to dispose of objects.

As a tl;dr of the “How to dispose of things” page — at the very minimum you must call renderer.dispose() to destroy the WebGL context and GPU resources. If you’ve created a render loop or event callbacks in your code, you’d need to disable those as well.

3 Likes

You must dispose assets that aren’t seen by the camera to ease your browser memory. See similar issue memory leak.

  1. Since you mention activity, is there a chance that you forget to stop the rendering loop? Rendering will not stop just because you deleted the canvas / closed the modal (although it will stop when you close the browser eventually):
const canvas = document.querySelector('canvas');
const renderer = new THREE.WebGLRenderer(/* ... */);

const loop = () => {
  requestAnimationFrame(loop);

  renderer.render(scene, camera);
};
loop();

canvas.remove(); // NOTE The 'loop' above will keep running and keep doing the calculations as if it was still rendering stuff, it just won't have a canvas to render that stuff into.

Creating a new requestAnimationFrame will also not stop the previous execution - so you’ll keep stacking one loop over another.


  1. Yes, definitely for bigger scale world you can dispose things that are too far away from the camera to be seen either way :blush: Not sure if not a bit of an overkill in this case, since it’s only one pipe, that geometry ain’t filling the GPU anytime soon.
2 Likes

How do you stop the animation loop? I have seen no code for this, nor emphasis on taking this step.

That depends on how you do the animation loop - pls show code for how you do rendering. Could be as simple as:

let animationLoop = null;

const animate = () => {
  if (animationLoop) {
    cancelAnimationFrame(animationLoop);
  }
  animationLoop = requestAnimationFrame(() => animate());

  renderer.render(scene, canvas);
};

const stopAnimationLoop = () => {
  cancelAnimationFrame(animationLoop);
};

animate(); // NOTE Starts animation loop
stopAnimationLoop(); // NOTE Stops animation loop

animate();
animate(); // NOTE Starts a new animation loop and also cancels any previous loops, preventing double-rendering and memory issues

requestAnimationFrame and cancelAnimationFrame work similarly to setTimeout and clearTimeout in terms of cancellation.

2 Likes

I like this solution. Another solution is to put a test of a boolean variable inside the animate function. If from outside the function the variable is set to false, then when the animate function calls itself and finds that the variable is false, it simply exits rather than recursively calling itself.

I have added renderer.dispose() to my code; thanks for that advice. As mentioned below, I now disable my render loop as well.