Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Our player doesn't seem to be handling 'low mem' messages from iOS

Discussion in 'Editor & General Support' started by RandomUnityDev123, Aug 23, 2023.

  1. RandomUnityDev123

    RandomUnityDev123

    Joined:
    Oct 28, 2022
    Posts:
    8
    Hi,

    I'm soak testing an additive scene load followed by Destroy() over and over again.

    I see overall memory usage (as measured in XCode) increasing slightly (20mb or so) with each load.
    This part I expect.

    If I do this a few times and manually call Resources.UnloadUnusedAssets() and GC.Collect(), I see all my incremental memory being freed
    This part I expect

    If I do this enough times without calling R.UUA() and G.C(), my device will run out of memory and crash.

    This part is not expected. This suggest that Unity is not calling GC.Collect() because I only found explicit documentation that says Unity calls Resources.UnloadUnusedAssets(). Is it true that I have to call GC.Collect() myself?

    I noticed that our app implements applicationDidReceiveMemoryWarning in our app controller that only spits out logging and does nothing else.
    Could the presence of this no-op delegate in our game be interfering with Unity's ability to free memory when it gets a lowmem message?
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    Unity can‘t know whether it should unload this particular asset, or whether running UnloadUnusedAssets at this point won‘t trigger your app reloading exactly those assets, triggering another low memory issue and thus deadlocking the app in a vicious load/unload cycle.

    Freeing up memory is up to you. There is no automatism as far as I know.

    See also the lowMemory callback: https://docs.unity3d.com/ScriptReference/Application-lowMemory.html
     
  3. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,140
    GC.Collect deals with the managed (scripting, i.e. C#) Heap and is triggered automatically when a new managed allocation is requested without enough contiguous reserved heap space being available. The likelihood of it needing to ever be called explicitly is pretty darn low, lower than most expect, and is usually based on faulty assumptions about internal implementations.

    Resources.UnloadUnusedAssets, aka the "Asset GC" deals with unloading native (C/C++ Engine internal) memory which is used by Assets (think pixel data for a texture, Transform Hierarchy information for GameObjects, etc.) and is never triggered automatically! It is run as part of some other Engine API calls like destructive scene loads, scene loads where it is explicitly requested via a parameter or by calling it directly.

    Your callback subscription has nothing to do with it, though you could call R.UUA from there. But if I were you, I'd probably re-check my assumptions and reevaluate the use-case.
     
  4. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,140
    Also, to avoid making faulty assumptions: always measure!
    The Profiler and Memory Profiler are there to help you get more clarity via measurements.
     
  5. RandomUnityDev123

    RandomUnityDev123

    Joined:
    Oct 28, 2022
    Posts:
    8
    Do you see anything missing from this sequence of calls? Doing this many times in a row results in as many copies of assets (SkinnedMeshRenderers, etc) that have no references to them from anything in our managed code. I suspect it's the 'localstack_prefab' reference but it's a local/auto variable:


    stack frame 1: AssetBundleCreateRequest localstack_bundleReq = AssetBundle.LoadFromFileAsync("...");
    stack frame 1: yield return localstack_bundleReq;
    stack frame 1: m_bundle = localstack_bundleReq.assetBundle;
    stack frame 1: localstack_bundleReq goes out fo scope
    ...
    ... some frames pass
    ...
    stack frame 2: AssetBundleRequest localstack_assetRequest = m_bundle.LoadAssetAsync<GameObject>("...");
    stack frame 2: yield return localstack_assetRequest;
    stack frame 2: GameObject localstack_prefab = localstack_assetRequest.asset as GameObject;
    stack frame 2: m_go = Instantiate(localstack_prefab);
    stack frame 2: localstack_prefab goes out of scope
    stack frame 2: localstack_assetRequest goes out of scope
    ...
    ... many frames pass
    ...
    stack frame 3: Destroy(m_go); m_go = null;
    stack frame 3: m_bundle.Unload(); m_bundle = null;
     
  6. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,140
    Have you looked at this with the Memory Profiler? You can assign some unique names to localstack_prefab.name (after instantiating m_go) and m_go.name and then look for both in the Memory Profiler.

    That way neither of us have to guess about the specifics of the AssetBundleRequest.asset property.

    If by
     auto 
    you mean a variable that will dispose itself on going out of scope, then no, that'd require a
    using var
    . I'm a little fuzzy on Prefabs at runtime, as technically only GameObjects exist at runtime, but it could be a GO with HideFlags.IsPersistent, aka an Asset. As such it might need Resources.UnloadUnusedAssets, or for less overhead and more precise unloading, just passing true to the AssetBundle.Unload call you have there already.

    If you take some Memory snapshots before and after the unloading, you should be able to get some details on that, including the HideFlags
     
  7. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,140
    Also be sure to profile this on device, it might behave differently in the Editor (where the Prefab type does still very much exist)
     
  8. RandomUnityDev123

    RandomUnityDev123

    Joined:
    Oct 28, 2022
    Posts:
    8
    The orphaned allocations have names that don't have the "(clone)" suffix, and they have IsPersistent as their only flag, which is a good lead. Gonna call m_bundle.Unload(true) and see if that makes a difference. Thanks for your help!
     
    MartinTilo likes this.