WeakRef 可构建不阻碍垃圾回收的纹理缓存池,核心是“尽可能保留、绝不阻止销毁”;需配合 FinalizationRegistry 实现惰性加载与自动清理,避免内存泄漏。

WeakRef 可以帮助你构建一个不阻碍垃圾回收的纹理缓存池,特别适合 3D 场景中大量纹理(如 PBR 材质贴图、Mipmaps、HDR 环境贴图)的按需加载与自动释放。它的核心价值不是“永久缓存”,而是“尽可能保留、但绝不阻止销毁”——这正契合纹理资源在 GPU 内存紧张或场景切换时应被及时释放的需求。
为什么不用 Map 或普通对象做纹理缓存?
传统缓存(如 Map<string texture></string>)会强持有纹理对象,即使该纹理已从所有 3D 对象(如 Mesh、Material)中移除,只要还在缓存里,JS 引擎就无法回收它,GPU 内存也无法释放,极易导致内存泄漏或 OOM。WeakRef 则绕过这一限制:它不阻止目标对象被 GC,只在对象尚存活时提供访问通道。
用 WeakRef + FinalizationRegistry 构建惰性缓存池
单靠 WeakRef 不足以实现“缓存池”语义(如容量控制、淘汰策略、重新加载),必须配合 FinalizationRegistry 监听纹理销毁,并维护元信息索引。典型结构如下:
-
缓存主干:用
Map<string ref: weakref>, lastUsed: number }></string>存储弱引用和使用时间戳 -
销毁监听:注册
FinalizationRegistry,纹理被 GC 时触发回调,从 Map 中清理对应条目 -
加载逻辑:请求纹理时先查缓存;若
ref.deref()返回 undefined,说明已被回收,触发异步加载并更新缓存 - 惰性策略:不预加载,不常驻;仅在首次绑定到材质或渲染前一刻才加载;支持带优先级的加载队列(如可见性、距离加权)
关键注意事项与实践细节
WeakRef 在 WebGL/Three.js 等环境中需谨慎处理生命周期边界:
-
纹理对象必须是 JS 托管对象:例如 Three.js 的
Texture实例(含.image、.gpuTexture等),而非底层 GPU handle(WebGPU 的GPUTexture不可被 WeakRef 持有) -
避免在 deref 后长期持有返回值:
ref.deref()返回的是原始对象引用,若你把它又存进普通 Map 或赋给全局变量,就等同于强引用,失去 WeakRef 意义 -
FinalizationRegistry 回调不保证及时性:GC 时间不可控,因此不能依赖它做资源同步清理(如解除 GPU 绑定),而应结合显式
texture.dispose()和弱缓存双重管理 -
搭配 LRU 清理逻辑更稳妥:WeakRef 不提供容量控制,建议定期扫描 Map,按
lastUsed时间剔除最久未用项(即使其仍存活),防止缓存无限膨胀
一个极简可运行示例(Three.js 场景)
以下为简化版纹理缓存池骨架,省略加载器与错误处理:
class LazyTexturePool {
#cache = new Map();
#registry = new FinalizationRegistry(key => this.#cache.delete(key));
constructor() {
// 注册时把 key(如 url)作为 holdTarget,确保能定位到缓存项
}
async get(url) {
const entry = this.#cache.get(url);
if (entry) {
const texture = entry.ref.deref();
if (texture) {
entry.lastUsed = performance.now();
return texture;
}
}
// 加载新纹理
const texture = await this.#loadTexture(url);
const key = url;
this.#cache.set(key, {
ref: new WeakRef(texture),
lastUsed: performance.now()
});
this.#registry.register(texture, key, { texture }); // 触发清理用
return texture;
}
#loadTexture(url) {
return new Promise(resolve => {
const loader = new THREE.TextureLoader();
loader.load(url, resolve);
});
}
}
使用时:const tex = await pool.get('./metal_roughness.jpg'); —— 它会在需要时加载,不再被引用后由 GC 自动清理,无需手动调用 pool.clear()。
不复杂但容易忽略:WeakRef 不是银弹,它是对“缓存-释放”契约的轻量表达。真正健壮的惰性纹理池,还需结合纹理压缩格式(Basis Universal)、流式 Mipmap 生成、GPU 内存压力反馈(如 navigator.gpu?.requestAdapter?.() 配合节流)等协同设计。

















