MonoTouch: App killed for low mem, Why? Live bytes

2020-02-29 11:33发布

My iPad app is developed in MonoTouch, because I wanted to avoid all the memory management hell, but it doesn't seem the case. On the simulator everything works fine, but when I tested my app on the device I discovered with horror that it is quickly killed by the OS after some memory warnings. My app is a simple image browser, it loads some PNG images and shows them using some UIViews inside an UIScrollView, loading the next or the previous when getting a touch. On the simulator it works fine. But on the device after loading and unloading about 6-11 images it start getting memory warnings and then suddenly the process is killed. I've checked all my instancing cycles, and I correctly delete all references before loading new images.

So I started Instruments and began profiling the memory allocation of my App on the iPad. There I discovered that the Live bytes are only around 5-9 MB, just what I expected, but for some strange reason the dead memory allocations are almost not collected at all, because after allocating about 50 MB (less than 5-9 MB of it being Live Bytes) it is being killed! Here is a screenshot of the Instruments profiling session of my App:

Instruments screenshots documenting memory allocation problem under monotouch

And here is the heapshots sequence:

heapshots of my app in instruments

There are also some small leaks, but I think that they are not big enough to be the culprits. They are all 48 bytes leaks from strdup, a known issue when releasing UIScrollView in iOS 5.1:

enter image description here

Even with everything that seems ok and the Live Bytes allocated are still at 5 MB, the REAL memory of my app grows exponentially before being killed up to 50MB on iPad, and up to 314 MB on the iPhone4S, as reported by the Memory Monitor:

memory growth of my App on iPhone4S

Does somebody can tell me if there is a method, or an utility to discover what and where the problem is? Is it a bug of the monotouch garbage collector? Or are there some object I don't dispose correctly of? And how I can find those with the profiler? I've checked my code for two days, but everything seems right.

Here is my code for the loading/instancing/disposing cycle:

    void StartImageLoadingThread()
{
    tokenSource = new CancellationTokenSource ();
    token = tokenSource.Token;
    Task task1 = new Task( () => PerformLoadImageTask(token),token);
    task1.Start();
}


void PerformLoadImageTask(CancellationToken token)
{
        if (token.IsCancellationRequested)
                {
                     Console.WriteLine("Task {0}: Cancelling", Task.CurrentId);
                return;
                }

            current_uiimage_A = UIImage.FromFileUncached(file_name);
            page_A_image_view.Image = current_uiimage_A;


} 


void UnloadImageAFromMemory()
{
      page_A_image_view.Image = null;
      current_uiimage_A.Dispose();
      current_uiimage_A = null;
}   

Is there a method to catch an object that is not been disposed correctly? Instruments reports that the live bytes are low, leaks are almost zero, then why the dead objects are not disposed of? Even if nothing happens in my app for minutes, the GC doesn't seem to make his job, even if I call it explicity in my code. I even tried the new experimental Garbage Collector, but it is even worse and quicker to make my app killed.

I cannot find any guideline or step-by-step guide on the Xamarin website for troubleshooting memory warnings or tracking down bad allocations.

Any help or suggestion is very appreciated, Thanks!

UPDATE: I've reduced the size and the resolution of some images and uiviews, and the Live Bytes are now dropped from 9 MB to 5 MB top, but the app is still killed after some memory warnings no matter what.

UPDATE 2: As suggested by Javier I've removed the background tasks and made the calls directly and the memory leak is gone! It seems that the MonoTouch garbage collector have a bug and is unable to collect memory when UIImages are allocated and disposed in a different thread. But now my app is awfully unresponsive when I scroll, so I need to find a solution to do it in a different thread! But how?

3条回答
走好不送
2楼-- · 2020-02-29 12:16

You need to use NSAutoreleasePools around your image creation code, something like this:

void PerformLoadImageTask(CancellationToken token)
{
    if (token.IsCancellationRequested)
    {
         Console.WriteLine("Task {0}: Cancelling", Task.CurrentId);
         return;
    }

    using (var pool = new NSAutoreleasePool ()) {
        current_uiimage_A = UIImage.FromFileUncached(file_name);
        page_A_image_view.Image = current_uiimage_A;
    }
}

For some APIs that create objects the object will be added to the autorelease pool, and not released until the pool is drained. MonoTouch automatically creates autorelease pools for all user threads (including threadpool, TPL and normal System.Threading threads), but these pools aren't drained until the thread exits (which you can't control in the case of threadpool and TPL threads). So the solution is to use a NSAutoreleasePool around the critical code (the pool will automatically be drained when disposed).

查看更多
SAY GOODBYE
3楼-- · 2020-02-29 12:16

This is interesting for me, I am just starting to look at memory management, I'm not even sure if it's essential to null and object and call dispose, because the scoping rules should do the job for you? However it will help the garbage collector to do it ASAP.

Explicitly asking for the garbage collector to run is a request, not a guarantee.

I would guess that an object created on the ui thread is being referenced in one outside of the ui thread and would look there, but I'm not sure.

I look forward to seeing an update in this as I'm sure that I will experience similar problems!

Good luck, Rob

查看更多
Bombasti
4楼-- · 2020-02-29 12:28

I had problems with UIImage.FromFile. My app is loading a lot of png images using a task, and showing it in the main thread.

I added a GC.Collect in the background task, but it hasn't fixed the problem. I had to remove the background task, do all the stuff in the main thread AND call GC.Collect. It seems that Image.Dispose is not releasing the image memory :(

But it doesn't work when you have another task, so i had to remove it :(

Yes it is not working....

查看更多
登录 后发表回答