查找 Unity ManagedStaticReferences

当我们使用 Unity Profiler 查看内存时,经常有些贴图等资源的引用只有一个 ManagedStaticReferences() 引用,怎么都卸载不掉。 使用 Memory Profiler 也查找不到谁引用的。现在终于找到方法,开心,分享给大家。

具体思路:

  1. 维护一个 key 是 物件 Hierarchy 路径, value 是 WeakReference 的字典,收集所有可能会泄漏的组件
  2. 在需要 Check 的物件挂靠的脚本里 添加该 Componet 的 弱引用到字典
  3. 查看 Alive 状态,Alive 为 true ,但 target 为空的即为泄漏者,打印他的路径
  4. 如果单纯查找 UI 贴图的内存泄漏,例如 NGUI 可以只在 UIWidget 的 Awake 里添加弱引用到字典, UGUI 的话需要下载 UGUI 源码 在 Graphic 构造里添加引用,再打包 dll 来测试。这样就可以覆盖所有 UI 贴图
  5. 其他的,怀疑那个脚本,就把他的引用添加到字典里

代码如下:

public class ComponentReferenceManager
{
    public static ComponentReferenceManager Instance = new ComponentReferenceManager();
    Dictionary<string, WeakReference> refs = new Dictionary<string, WeakReference>();

    private int count = 0;
    public void AddRef(Component c)
    {
        var key = string.Format("Index:<color=red>{0}</color> ComponentType:<color=red>{1}</color> GameObject:<color=red>{2}</color>", 
                  count,  c.GetType().ToString(), GetGameObjectPath(c));

        refs[key] = new WeakReference(c);
        ++count;
    }

    private string GetGameObjectPath(Component c)
    {
        var obj = c.gameObject;
        string path = "/" + obj.name;
        while (obj.transform.parent != null)
        {
            obj = obj.transform.parent.gameObject;
            path = "/" + obj.name + path;
        }
        return path;
    }

    public void PrintLog()
    {
        GC.Collect();
        Debug.LogError("打印销毁了但还被引用的物件:");
        foreach(var kv in refs)
        {
            if (kv.Value.IsAlive)
            {
                if (kv.Value.Target == null)
                {
                    Debug.LogErrorFormat("Target is null {0}", kv.Key);
                    continue;
                }

                var w = kv.Value.Target as Component;
                if (w == null)
                {
                    Debug.LogErrorFormat("Component is null {0}", kv.Key);
                    continue;
                }

                if (w.gameObject == null)
                {
                    Debug.LogErrorFormat("Component attached game object is null {0}", kv.Key);
                }
            }
        }
    }
}

UIWidget 的 Awake 里 或者你怀疑的其他脚本里添加调用

    #if UNITY_EDITOR
        ComponentReferenceManager.Instance.AddRef(this);
    #endif

编辑器脚本

public partial class ReportEditor
{
    [MenuItem("Jinggle/打印销毁了但还被引用的物件")]
    static void ReportUIRef () 
    {
        ComponentReferenceManager.Instance.PrintLog();
    }
}

这样就会找到所有在场景里已经销毁了,但还是被引用到的物件了。 下一步就是要查为什么明明被销毁了,还会被引用的原因了。

经过我排查我们游戏,发现导致 ManagedStaticReferences() 绝大部分原因都是 static 变量的使用不当。 真真儿是 静态变量一时爽,内存泄漏火葬场。

--EOF--

王在京同学 /
Published under (CC) BY-NC-SA in categories Unity3d  tagged with Unity3d