Why Three.js Leaks GPU Memory
JavaScript's garbage collector handles CPU memory automatically. But
Three.js objects also hold GPU resources — buffer
objects, textures, framebuffers — that are managed through the WebGL
API. The garbage collector doesn't know about these. When you discard
a Mesh by removing the JavaScript reference, the GPU
memory stays allocated until you explicitly call
dispose().
In a long-running simulation that creates and discards objects regularly, this adds up to hundreds of megabytes of leaked VRAM, eventually causing crashes or severe performance degradation.
Symptom check: Open Chrome DevTools → Performance → Memory. If GPU memory steadily increases while running your simulation (even when nothing is visually changing), you have a leak.
The Complete Checklist
Geometry
geometry.dispose(); // Frees vertex buffers, index buffers
- Call on every
BufferGeometryyou create - Instanced mesh geometries also need disposal
-
Merged geometries (from
mergeGeometries) are new objects — dispose them too
Materials
material.dispose(); // Frees the compiled shader program
- Call on every
Materialsubclass - Material arrays (multi-material meshes): loop and dispose each element
- Does NOT automatically dispose textures — see below
Textures
// Manual texture disposal
material.map?.dispose();
material.normalMap?.dispose();
material.roughnessMap?.dispose();
material.envMap?.dispose();
// ... all map properties
// Or use traversal
scene.traverse((obj) => {
if (obj.material) {
Object.values(obj.material).forEach(val => {
if (val?.isTexture) val.dispose();
});
}
});
Render Targets
renderTarget.dispose(); // Frees FBO + attached textures
The Full Teardown Pattern
function disposeMesh(mesh) {
mesh.geometry?.dispose();
if (Array.isArray(mesh.material)) {
mesh.material.forEach(disposeMaterial);
} else {
disposeMaterial(mesh.material);
}
}
function disposeMaterial(mat) {
if (!mat) return;
mat.dispose();
// Dispose all map textures
['map','normalMap','roughnessMap','metalnessMap',
'aoMap','emissiveMap','envMap','lightMap',
'bumpMap','displacementMap','alphaMap']
.forEach(key => mat[key]?.dispose());
}
// Scene teardown
scene.traverse((obj) => {
if (obj.isMesh) disposeMesh(obj);
});
renderer.dispose();
renderer.forceContextLoss();
InstancedMesh
InstancedMesh is just a Mesh — dispose its
geometry and material the same way. The
instanceMatrix and instanceColor buffer
attributes are freed when the geometry is disposed.
Pro tip: Call
renderer.info.reset() between scenes and log
renderer.info.memory to verify geometry and texture
counts drop to zero after your teardown. If they don't, you missed a
dispose somewhere.