Unity WebGL资产包内存无法释放

7

我正在使用以下函数在Unity WebGL中加载和缓存资源包

 IEnumerator DownloadAndCacheAB(string assetName)
    {
        // Wait for the Caching system to be ready
        while (!Caching.ready)
            yield return null;

        // Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
        using (WWW www = WWW.LoadFromCacheOrDownload(finalUrl, 1))
        {

            yield return www;

            if (www.error != null)
            {
                Debug.Log("WWW download had an error:" + www.error);
            }

            AssetBundle bundle = www.assetBundle;

            if (assetName == "")
            {
                GameObject abObject = (GameObject)Instantiate(bundle.mainAsset, gameObject.transform.position, gameObject.transform.rotation);
                abObject.transform.parent = this.transform;
                SetTreeShaderSettings(abObject);
            }
            else
            {
                GameObject abObject = (GameObject)Instantiate(bundle.LoadAsset(assetName), gameObject.transform.position, gameObject.transform.rotation);
                abObject.transform.parent = this.transform;

            }

            // Unload the AssetBundles compressed contents to conserve memory
            bundle.Unload(false);

        } // memory is freed from the web stream (www.Dispose() gets called implicitly)
    }

每当我想要移除对象时,我使用这个函数。

 public void RemoveBundleObject()
    {
        //if (www != null)
        //{
        //    www.Dispose();
        //    www = null;
        if (loadBundleRef != null)
        {
            StopCoroutine(loadBundleRef);
        }
        if (this.gameObject.transform.childCount > 0)
        {
            Destroy(this.gameObject.transform.GetChild(0).gameObject);
            System.GC.Collect();
        }
        //}
    }

正如您所看到的,我正在删除第一个子项(从资源包中获取),然后调用GC Collect来强制垃圾收集器。但问题是,当我卸载/销毁对象时,我的内存并没有释放。现在您可能会想,我如何测量内存?我正在使用这里的WebGLMemoryStats。经过几次迭代的assetbundle加载后,我得到了这个结果。

enter image description here

编辑: 我现在正在使用WebRequest类来下载assetbundle(在这里找到),但仍然无法释放内存,有时会出现aw snap错误。

即使我一遍又一遍地尝试加载一个空的webgl构建,它也会增加内存堆,然后有时我会遇到aw snap或内存错误,Chrome崩溃。

enter image description here

正如您所看到的,初始加载大约为1.2 Mb,但当我刷新页面并拍摄快照时,快照会带有增加的内存。


你说刷新页面时会发生这种情况... 你确定在那种情况下调用了 RemoveBundleObject 吗? - derHugo
当您刷新页面时,此函数不会被调用。 - Muhammad Faizan Khan
即使在Unity2018.3.8中创建一个空的WebGL项目并重新加载它并进行快照,您也会发现堆内存增加。 - Muhammad Faizan Khan
2个回答

3
作为一条提示,你永远不会使这个功能完美地运作。尽管有清理垃圾和卸载资源的工具,但即使你遵循所有最佳实践,仍然可能无法使Unity清理所有垃圾(它在后台执行很多操作,这些操作会保留分配的内存,你几乎无法对此做任何事情)。如果你想知道原因,我建议阅读他们关于堆碎片以及内存优化指南的文档。
就你正在进行的项目而言,至少有一些改进可以做到:
1) 尽量避免使用WWW类。它会产生更多的垃圾,不总是清理干净,并且在最新版本的Unity中已经过时。使用UnityWebRequest来下载bundle。
2) 不要养成仅为加载单个资源而加载bundle,然后卸载该bundle的习惯。如果在运行时有大量加载发生,这将导致抖动并且是管理bundle的一种相当低效的方式。我注意到你正在调用bundle.Unload(false),这意味着bundle正在被卸载,但是已加载的prefab资产则没有被卸载。我建议以一种能够:
  1. 加载所需的bundle
  2. 从该bundle中加载所需的资产
  3. 等待所有这些资产的寿命结束
  4. 调用bundle.Unload(true)
的方式重新组织此内容。

3) 谨慎使用StopCoroutine(loadBundleRef);(如果loadBundleRef是运行您的网络请求和包加载逻辑的Coroutine对象)。中断这些异步操作可能会导致内存问题。您应该采取措施确保Web请求和包加载要么完全完成,要么在失败时抛出并让游戏恢复。不要允许像StopCoroutine这样的东西打断它们。

4)System.GC.Collect();很慢,垃圾回收也会定期进行。请谨慎使用,并在调用之前可能还需要调用Resources.UnloadUnusedAssets


感谢您的建议,但是我想从缓存中加载以实现快速加载,UnityWebRequest可能没有提供此功能。我使用stop coroutine,因为如果用户想要取消加载,则可以立即取消请求以节省内存,但这并没有正常工作。调用GC collect没有重要性,内存会不断增加。 - Muhammad Faizan Khan
@MuhammadFaizanKhan 1) 我不知道你使用的Unity版本是什么,但早在2017.4(甚至更早),Caching功能与UnityWebRequest一起工作WWW生成更多垃圾,并且在新版本的Unity中已经过时;问问Unity的任何人,他们都会告诉你永远不要使用它。2) 你不能取消一个正在进行中的WWW请求并期望它被正确清理;你必须让它完成,然后再清理它。 - Foggzie
GC.Collect()而言,堆碎片文章(答案中提供链接)将告诉您为什么它实际上并没有减少内存。您的内存最终会变得分散,因此在Unity已经分配的空间中有可用内存,但在您关闭Unity之前,它不会返回到操作系统。如果无法使用新的分配填充这些空缺,它将从操作系统中分配更多空间。 - Foggzie
感谢您宝贵的建议,尽管我会尝试您的最佳实践,但由于我必须加载如此多的资产包,在Unity中完成这项工作可能会很困难。我的项目范围非常大,您的答案和评论表明这不是Unity易于处理的工作。 - Muhammad Faizan Khan

0
请考虑使用@Foggzie的答案,其中包含一些最佳实践(我使用了其中一些)!
我的代码或测试程序有两个问题。
1. 不要使用WWW.LoadFromCacheOrDownload
Unity内置的WWW.LoadFromCacheOrDownload太烂了!真的!为什么?Ben Vinsonunity在他们的博客中也有解释:
另一个与内存相关的问题源是Unity使用的IndexedDB文件系统。每当您缓存资源包或使用任何与文件系统相关的方法时,它们都会存储在由IndexedDB支持的虚拟文件系统中。
您可能没有意识到的是,这个虚拟文件系统会在Unity应用程序启动时被加载并持久化在内存中。这意味着,如果您正在使用默认的Unity缓存机制来缓存资源包,即使它们没有被加载,您也会将所有这些资源包的大小添加到游戏的内存需求中。
而Unity在他的博客中明确提到要使用UnityWebRequest而不是LoadFromCacheOrDownload。
一种更长期的解决方案是使用WWW构造函数而不是LoadFromCacheOrDownload()或者使用UnityWebRequest.GetAssetBundle(),如果你正在使用新的UnityWebRequest API,则不需要哈希/版本参数。然后,在XMLHttpRequest级别上使用替代缓存机制,将下载的文件直接存储到indexedDB中,跳过内存文件系统。这正是我们最近开发的内容,并在资产商店上提供。欢迎在您的项目中使用它,并根据需要进行自定义。
2.当Chrome Dev工具打开时不要测量内存
页面重新加载时内存增量问题似乎与Firefox内部机制有关。但是,在我检查内存时,由于Dev工具打开,这也会在Chrome中发生。当我在Chrome中测量我的标签页内存时,Dev工具是打开状态,所以每次刷新页面时内存都会增加。这个原因被Unity官方论坛的某位定义。
需要注意的一点是,在Firefox中对页面重新加载进行内存使用率分析时,请确保关闭Firefox Web控制台(和调试器)窗口。Firefox有一个行为,即如果Web控制台处于打开状态,则会使Firefox JS调试器保持活动状态,这会将所有访问过的页面固定在内存中,永远不会回收它们。关闭Firefox网页控制台可以释放旧页面的内存。

我不知道Chrome是否可能具有类似的行为,但为了保险起见,最好关闭其Web控制台以确保它不会保持页面活动状态。

如果有人有可公开发布的公共测试用例,那么这将有助于确定问题的来源。

但实际上我的测试表明,这个问题在火狐浏览器中仍然存在,而在Google Chrome中已经解决。

请记住,我经过长时间的奋斗才得到这个答案。

更新:我发现在Firefox中重新加载页面会使页面的内存增加一倍。因此,这是Firefox的问题,与Unity WebGL无关。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接