1. 内存管理与垃圾回收

1.1. 内存管理核心原则

Three.js不会自动释放WebGL资源,必须手动管理:
javascript

// 清理几何体
geometry.dispose();

// 清理材质
material.dispose();

// 清理纹理
texture.dispose();

// 清理渲染器(重置WebGL上下文)
renderer.dispose();

场景清理的最佳实践

javascript

function cleanupScene(scene, renderer) {
    scene.traverse((object) => {
        if (object.geometry) {
            object.geometry.dispose();
        }
        if (object.material) {
            if (Array.isArray(object.material)) {
                object.material.forEach(material => material.dispose());
            } else {
                object.material.dispose();
            }
        }
        if (object.texture) {
            object.texture.dispose();
        }
    });

    // 清空场景
    while(scene.children.length > 0) {
        scene.remove(scene.children[0]);
    }

    // 可选:清除renderer缓存
    renderer.forceContextLoss();
}

1.2. 常见内存泄漏场景

未被移除的对象引用

javascript

// ❌ 错误的做法
function createTemporaryObject() {
    const geometry = new THREE.BoxGeometry();
    const material = new THREE.MeshBasicMaterial();
    const mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    // 即使从场景移除,geometry/material仍被引用
    scene.remove(mesh);
    // 需要手动释放!
    // geometry.dispose(); // 忘记调用
    // material.dispose(); // 忘记调用
}

缓存问题

javascript

// Three.js内部缓存可能导致内存泄漏
THREE.Cache.enabled = false; // 谨慎使用,可能影响性能

// 清除特定缓存
THREE.Cache.remove(key);

1.3. 常见内存泄漏场景

 未被移除的对象引用

javascript

// ❌ 错误的做法
function createTemporaryObject() {
    const geometry = new THREE.BoxGeometry();
    const material = new THREE.MeshBasicMaterial();
    const mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    // 即使从场景移除,geometry/material仍被引用
    scene.remove(mesh);
    // 需要手动释放!
    // geometry.dispose(); // 忘记调用
    // material.dispose(); // 忘记调用
}

 缓存问题

javascript

// Three.js内部缓存可能导致内存泄漏
THREE.Cache.enabled = false; // 谨慎使用,可能影响性能

// 清除特定缓存
THREE.Cache.remove(key);

1.4. 性能监控与调试

内存监控工具

javascript

// 监控WebGL内存
console.log(renderer.info.memory);

// 输出信息包括:
// geometries: 几何体数量
// textures: 纹理数量
// programs:着色器程序数量

 Chrome DevTools 监控

1.5. 最佳实践指南

 资源复用

javascript

// 复用几何体和材质
const geometries = {};
const materials = {};

function getGeometry(type, size) {
    const key = `${type}_${size}`;
    if (!geometries[key]) {
        geometries[key] = createGeometry(type, size);
    }
    return geometries[key].clone(); // 注意:clone()创建新实例
}

 智能加载管理

javascript

class ResourceManager {
    constructor() {
        this.resources = new Map();
    }

    loadTexture(url) {
        if (this.resources.has(url)) {
            return this.resources.get(url);
        }

        const texture = new THREE.TextureLoader().load(url, () => {
            texture.isLoaded = true;
        });

        this.resources.set(url, texture);
        return texture;
    }

    cleanupUnused() {
        for (const [url, texture] of this.resources) {
            if (!texture.isUsed && texture.isLoaded) {
                texture.dispose();
                this.resources.delete(url);
            }
        }
    }
}

 动态场景管理

javascript

class LODManager {
    constructor(scene, camera) {
        this.scene = scene;
        this.camera = camera;
        this.visibleObjects = new Set();
    }

    update() {
        const tempVisible = new Set();

        // 视锥体剔除
        const frustum = new THREE.Frustum();
        const matrix = new THREE.Matrix4()
            .multiplyMatrices(
                this.camera.projectionMatrix, 
                this.camera.matrixWorldInverse
            );
        frustum.setFromProjectionMatrix(matrix);

        this.scene.traverse((obj) => {
            if (obj.geometry && frustum.intersectsObject(obj)) {
                tempVisible.add(obj);
                if (!this.visibleObjects.has(obj)) {
                    obj.visible = true;
                }
            }
        });

        // 隐藏不可见对象
        for (const obj of this.visibleObjects) {
            if (!tempVisible.has(obj)) {
                obj.visible = false;
            }
        }

        this.visibleObjects = tempVisible;
    }
}

1.6. 垃圾回收策略

 JavaScript 对象管理

javascript

// 使用WeakMap存储临时引用
const weakReferences = new WeakMap();

// 避免循环引用
class GameObject {
    constructor() {
        this.mesh = null;
        this.animations = [];
    }

    destroy() {
        // 打破循环引用
        this.animations.forEach(anim => anim.target = null);
        this.animations = null;

        if (this.mesh) {
            this.mesh.geometry.dispose();
            this.mesh.material.dispose();
            this.mesh.parent?.remove(this.mesh);
            this.mesh = null;
        }
    }
}

 定时清理

javascript

class MemoryManager {
    constructor(interval = 30000) { // 30秒清理一次
        this.interval = interval;
        this.startCleanupCycle();
    }

    startCleanupCycle() {
        setInterval(() => {
            this.cleanupUnusedResources();
            this.forceGarbageCollection();
        }, this.interval);
    }

    cleanupUnusedResources() {
        // 清理未使用的纹理
        renderer.getContext().flush();
    }

    forceGarbageCollection() {
        /** 清理不再使用的资源
            geometry.dispose();
            material.dispose();
            texture.dispose();
        **/
    }
}

2. 调试技巧

  1. 使用stats.js监控

javascript

import Stats from 'stats.js';

const stats = new Stats();
stats.showPanel(0); // 0: fps, 1: ms, 2: mb
document.body.appendChild(stats.dom);

function animate() {
    stats.begin();
    // 渲染逻辑
    stats.end();
}
  1. Chrome Memory Profiler

3. 关键建议

  1. 始终在移除对象时调用dispose()

  2. 定期监控renderer.info.memory

  3. 避免在动画循环中创建新对象

  4. 使用对象池复用资源

  5. 及时移除事件监听器

  6. 对于大型场景,实现分块加载/卸载

通过遵循这些原则,你可以显著减少Three.js应用的内存占用,避免内存泄漏,确保应用长期稳定运行。