查找 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