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

A weird memory heap leak issue on android

Discussion in 'Editor & General Support' started by meichen8050753, Feb 21, 2020.

  1. meichen8050753

    meichen8050753

    Joined:
    Jan 16, 2017
    Posts:
    36
    1.Alloc a large memory on heap save to _largeHeapObjects list
    2.Alloc temp heap every frame for morment
    3.Stop frame alloc and clear the _largeHeapObjects the call GC.Collect();
    but sometime the managedHeap.UsedSize not set to start size.


    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Profiling;
    6. using UnityEngine.Scripting;
    7.  
    8. public class TestMain : MonoBehaviour
    9. {
    10.  
    11.     public int allocTempHeapSizeBytes = 1024 * 1024 * 10;//10MB
    12.  
    13.  
    14.     private bool AllocLargeMemoryPermanent = false;
    15.     private bool AllocLargeMemoryTemp = false;
    16.  
    17.  
    18.     private List<byte[]> _largeHeapObjects = new List<byte[]>();
    19.  
    20.     void Start()
    21.     {
    22.         //GarbageCollector.GCMode = GarbageCollector.Mode.Disabled;
    23.     }
    24.  
    25.     // Update is called once per frame
    26.     void Update()
    27.     {
    28.         if(AllocLargeMemoryTemp == true) {
    29.  
    30.             if( Time.frameCount % 10 == 0) {
    31.                 var size = UnityEngine.Random.Range( 1,  allocTempHeapSizeBytes );
    32.                 var bytes = new byte[size];
    33.                 Array.Clear( bytes, 0, bytes.Length );
    34.                 bytes = null;
    35.             }
    36.  
    37.         }
    38.     }
    39.  
    40.     public void onAllocLargeMemoryPermanentOnce() {
    41.         AllocLargeMemoryPermanent = true;
    42.         var amount = 1024 * 1024 * 1;
    43.         var itemSize = 512;
    44.         _largeHeapObjects = new List<byte[]>( amount );
    45.         for (int i = 0; i < amount; i++) {
    46.             _largeHeapObjects.Add(new byte[itemSize]);
    47.         }
    48.         GC.WaitForPendingFinalizers();
    49.     }
    50.  
    51.     public void onAllocLargeMemoryTempEveryNFrame() {
    52.         AllocLargeMemoryTemp = true;
    53.     }
    54.  
    55.     public void onStopAllocTempHeap() {
    56.         AllocLargeMemoryTemp = false;
    57.         if(_largeHeapObjects!=null)
    58.             _largeHeapObjects.Clear();
    59.         _largeHeapObjects = null;
    60.         GC.Collect();
    61.         GC.WaitForPendingFinalizers();
    62.         Resources.UnloadUnusedAssets();
    63.     }
    64. }
    65.  
     

    Attached Files:

    alexeyzakharov likes this.
  2. alexeyzakharov

    alexeyzakharov

    Unity Technologies

    Joined:
    Jul 2, 2014
    Posts:
    506
    Thank you for taking time and making a perfect case with a project and a video!

    From the allocation pattern it seems that this is a case of a mono heap memory fragmentation.
    Unity uses non compacting and non moving GC (both mono and il2cpp) and thus temp allocations interleaving with persistent allocations could cause overall mem pressure grow. This is might be a real problem depending on a project scale and the solutions (besides minimizing of GC allocations) are generally:
    1. Pooling/Caching (reservation of persistent memory which can be reused in a different contexts) - higher mem pressure, but controlled fragmentation pattern.
    2. Using non-managed memory for temp allocations https://docs.unity3d.com/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.Malloc.html
     
  3. meichen8050753

    meichen8050753

    Joined:
    Jan 16, 2017
    Posts:
    36
    Thank you for replay.

    That is one question puzzled me. Why Boehm GC not recycle the memory that has no referece?
     
  4. alexeyzakharov

    alexeyzakharov

    Unity Technologies

    Joined:
    Jul 2, 2014
    Posts:
    506
    It does recycle the memory in a way that you can later use that address space for a new data. But e.g. the Mono heap is a list of large memory blocks (like 16-256MB). So when temp memory pressure is high and some persistent allocations happen to fall into a large block, afaik that whole block is not returned to the system after all temp memory is collected and one allocation stays.
     
  5. dairectx

    dairectx

    Joined:
    Jan 31, 2015
    Posts:
    5
    Sorry, let me try to ask the question more clearly:
    Is it a normal thing:
    In some cases,
    Profiler.GetMonoUsedSizeLong
    ( not the Profiler.GetMonoHeapSizeLong )
    will return a large number( e.g. 100MB ) even though ALL allocation is cleared, no reference, and run GC.Collect() like the sample script above?

    Because the video above described a interesting issue, even All objects should be GCed, the Profiler.GetMonoUsedSizeLong still reports the USED heap memory is 100+MB
     
  6. alexeyzakharov

    alexeyzakharov

    Unity Technologies

    Joined:
    Jul 2, 2014
    Posts:
    506
    Sorry, I should have read the text in the video carefully :)
    Did you try doing a double GC.Collect call? I would expect temp allocation sometime survive the sweeping pass.
    Like this
    Code (CSharp):
    1.         GC.Collect();
    2.         GC.WaitForPendingFinalizers();
    3.         GC.Collect();
     
  7. alexeyzakharov

    alexeyzakharov

    Unity Technologies

    Joined:
    Jul 2, 2014
    Posts:
    506
    Generally I would probably use Memory Profiler to see what actually stays in memory and why?
    There might be also some other allocations coming from Unity or UI pieces.
     
  8. dairectx

    dairectx

    Joined:
    Jan 31, 2015
    Posts:
    5
    Not exactly. Just pressed button 3 and run clear function
    onStopAllocTempHeap
    again and again.
    The usedmono wouldn't go down.

    Anyway, I'll try your code tomorrow.

    BTW, the cause ticket id is 1219233.
    Many thanks for your reply and interested in this.
     
    alexeyzakharov likes this.
  9. dairectx

    dairectx

    Joined:
    Jan 31, 2015
    Posts:
    5
    FYI, double GC don't help much, it still leaks.
    And yes, Memory Profile, I have already used it, no any big difference if you compare the normal snapshot(<10MB) and the leaked snapshot(>100MB).
    I did this again today for capture the screenshot.
    upload_2020-2-26_12-4-19.png
     
    alexeyzakharov likes this.
  10. alexeyzakharov

    alexeyzakharov

    Unity Technologies

    Joined:
    Jul 2, 2014
    Posts:
    506
    Thanks for verification! If memory profiler doesn't show it, it is not referenced from anywhere directly and the issue might happen:
    1. due to the mono api reporting "a lower bound on the number of free bytes in the heap (excluding the unmapped memory space)" used mono memory == GC_get_heap_size() - GC_get_free_bytes().
    2. allocations stuck somewhere in nursery/markup phase as even if there are no direct references to the object Boehm GC still might find something which looks like a memory pointer to that address and keep memory
    3. bug in vm/gc.
    I guess in case of 1 if after some allocations you don't see used memory increase it is a counting issue. 2 and 3 might be quite fundamental issues related to how gc works...
    Thanks for filing a bug!
     
    meichen8050753 and dairectx like this.
  11. meichen8050753

    meichen8050753

    Joined:
    Jan 16, 2017
    Posts:
    36
    I browse the code of boehmgc
    I found that it has blacklist.c and it‘s said :
    Code (CSharp):
    1. /*
    2. * We maintain several hash tables of hblks that have had false hits.
    3. * Each contains one bit per hash bucket;  If any page in the bucket
    4. * has had a false hit, we assume that all of them have.
    5. * See the definition of page_hash_table in gc_private.h.
    6. * False hits from the stack(s) are much more dangerous than false hits
    7. * from elsewhere, since the former can pin a large object that spans the
    8. * block, even though it does not start on the dangerous block.
    9. */
    It seems like the 2 that you point out.
     
    Last edited: Feb 28, 2020
    alexeyzakharov likes this.
  12. PedroDuran

    PedroDuran

    Joined:
    Aug 19, 2014
    Posts:
    32
    Anyone got this problem solved? :( i have a similar situation, in editor my script runs well but in android il2cpp build it does not seems to free memory.
     
    DankalApps likes this.
  13. DankalApps

    DankalApps

    Joined:
    Mar 8, 2023
    Posts:
    17
    I have similar problem. Memory leak on Android build, fine on PC+Editor.
    But instead of manual allocations, I just create Texture2D many times.

    I have main screen (scene 1) and game scene (scene2) with a lot of Texture2D objects.

    When moving between scenes (scene 1 -> scene 2 -> scene 1 -> scene 2 -> scene1 -> scene2) I get increasing managed heap memory for scene2, which eventually causes application crash on Android device. When I am trying to reproduce in Editor, on PC, there is no memory leak.

    How can I fix this / is there some workaround?

    Unity 2021.3.26f1.