You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 00:42:52