Unity内存泄漏问题:频繁重载纹理时触发TLS Allocator分配错误
Unity纹理实时重载内存泄漏排查与解决
问题分析
你的代码每秒执行10次纹理重载,出现TLS Allocator ALLOC_TEMP_TLS, underlying allocator ALLOC_TEMP_MAIN has unfreed allocations报错,核心原因集中在资源的频繁创建销毁、读取逻辑错误和协程并发问题:
- 频繁创建销毁Material:每次重载都销毁旧Material并新建实例,Unity的
Destroy是延迟到下一帧执行的,短时间内大量创建GPU材质资源,会导致内存堆积无法及时释放。同时每次调用Shader.Find也会带来额外开销。 - Texture2D未复用:每次重载都销毁旧纹理并新建
Texture2D,同样因Destroy延迟执行,GPU纹理资源无法及时回收,造成内存泄漏。 - 文件读取逻辑错误:
yield return new WaitUntil(() => fs.Read(textureData, 0, numBytesToRead) > 0)的写法存在问题:fs.Read可能一次读不完全部字节,但该逻辑会反复调用Read覆盖已读取的数据;若文件读取完成后Read返回0,协程会永久挂起,导致FileStream无法释放,占用内存。 - 协程并发冲突:每秒启动10个新协程,多个协程同时操作同一资源,会导致资源引用混乱,旧协程的资源无法被正确回收。
解决方法
1. 修复文件读取逻辑,确保完整读取且不阻塞协程
替换原读取代码,循环读取文件直到完成,避免协程挂起:
IEnumerator LoadTextureAsync(string textureDataPath, GameObject g) { byte[] textureData; using (FileStream fs = File.Open(textureDataPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { textureData = new byte[fs.Length]; int bytesRead = 0; while (bytesRead < textureData.Length) { int readCount = fs.Read(textureData, bytesRead, textureData.Length - bytesRead); if (readCount == 0) { // 读取失败,终止协程 yield break; } bytesRead += readCount; // 每一帧读取一部分,避免阻塞主线程 yield return null; } } // 复用Texture2D,无需销毁重建 if (tex2D == null) { tex2D = new Texture2D(2, 2); } tex2D.LoadImage(textureData); ApplyTextureToGameObject(g, tex2D); }
2. 复用Texture2D,避免频繁创建销毁
删除原代码中Destroy(tex2D)和tex2D = new Texture2D(2, 2)的逻辑,直接复用现有实例,LoadImage会自动调整纹理尺寸以匹配图片数据。
3. 复用Material,减少GPU资源开销
提前缓存Shader,复用现有Material实例,避免频繁创建销毁:
private Shader unlitTextureShader; void Start() { tex2D = new Texture2D(2, 2); // 缓存Shader,避免每次查找 unlitTextureShader = Shader.Find("Unlit/Texture"); } void ApplyTextureToGameObject(GameObject g, Texture2D texture) { Renderer renderer = g.GetComponent<Renderer>(); if (renderer != null) { if (renderer.material == null) { renderer.material = new Material(unlitTextureShader); } // 直接修改现有Material的纹理,无需重建 renderer.material.mainTexture = texture; } }
4. 控制协程并发,避免资源冲突
添加协程引用,每次启动新协程前停止旧协程,确保同一时间只有一个重载任务在执行:
private Coroutine currentLoadCoroutine; public void Load(string textureDataPath, GameObject g) { if (currentLoadCoroutine != null) { StopCoroutine(currentLoadCoroutine); } currentLoadCoroutine = StartCoroutine(LoadTextureAsync(textureDataPath, g)); }
5. 优化资源清理逻辑
修改OnDestroy,确保清理目标物体的Material(如果目标物体不是当前组件所属GameObject):
void OnDestroy() { if (tex2D != null) { Destroy(tex2D); } // 如果需要清理目标物体的Material,需持有目标引用,示例如下: // if (targetGameObject != null) // { // Renderer targetRenderer = targetGameObject.GetComponent<Renderer>(); // if (targetRenderer != null && targetRenderer.material != null) // { // Destroy(targetRenderer.material); // } // } }
内容的提问来源于stack exchange,提问作者HTTRCH
相关产品推荐
相关产品推荐

