Do all UWP apps leak memory when navigating pages?
Asked Answered
C

4

16

So I've been getting my teeth into UWP and developing a simple app in C# using VS2017 v15.6.4, on the latest release of Windows 10.

When running the app I notice its memory usage continues to increase over time.

After a lot of pairing back of the code, I've come to the conclusion that this is being caused by page navigation calls, such as:

Frame.Navigate(typeof SomePage);
Frame.GoBack();
Frame.GoForward();

It is very easy to create and observe this process...

1) In VS2017, create a new Blank App (Universal Windows) project, call it PageTest.

2) Add a new Blank Page to the project, naming it 'NewPage'.

3) Add the following code to the MainPage.xaml.cs:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace PageTest
{
    public sealed partial class MainPage : Page
    {
        DispatcherTimer timer = new DispatcherTimer();

        public MainPage()
        {
            InitializeComponent();
            timer.Interval = TimeSpan.FromSeconds(.01);
            timer.Tick += Timer_Tick;
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            timer.Start();
        }

        private void Timer_Tick(object sender, object e)
        {
            timer.Stop();
            Frame.Navigate(typeof(NewPage));
        }
    }
}

4) Add the following (almost identical) code to NewPage.xaml.cs:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace PageTest
{
    public sealed partial class NewPage : Page
    {
        DispatcherTimer timer = new DispatcherTimer();

        public NewPage()
        {
            InitializeComponent();
            timer.Interval = TimeSpan.FromSeconds(.01);
            timer.Tick += Timer_Tick;
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            timer.Start();
        }

        private void Timer_Tick(object sender, object e)
        {
            timer.Stop();
            Frame.Navigate(typeof(MainPage));
        }
    }
}

You can see that this simple test app contains 2 pages, and when it runs, the app will automatically navigate between the two pages at the rate of 100 times per second (via the timers) until you close the app.

5) Build and run the app. Also run Task Manager and note the app's initial memory footprint.

6) Go and make a cup of coffee. When you come back you'll see the memory usage has grown. And it will continue to grow.

Now I know this example is unrealistic, but it is here purely to demonstrate what I suspect is a fundamental problem affecting most (if not all) UWP apps.

Try this...

Run the Windows 10 Settings app (a UWP app developed by Microsoft). Again, note it's initial memory footprint in Task Manager. (On my kit this starts at about 12.1 MB).

Then repeatedly click on the System Settings icon... then the Back button... then the System Settings icon... then the Back button... You get the idea. And watch the memory footprint also increase.

After a few minutes of doing this, my MS Settings app memory consumption went up to over 90 MB.

This memory consumption seems to be related to UWP page complexity and it goes up rapidly if you start adding a lot of XAML controls to your pages, especially Image controls. And it doesn't take long before my feature rich UWP app is consuming 1-2GB memory.

So this 'problem' seems to affect all frame based UWP apps. I've tried it with other UWP apps on 3 different PC's and I see the same problem on them all.

With my feature rich app, memory consumption has got so bad that I'm now considering scrapping Page navigation altogether and putting everything on the MainPage. Which is not a pleasant thought.

Potential Solutions That Don't Work...

I've come across other articles describing a similar issue and there are proposed solutions that I've tried, which don't make any difference...

1) Adding either of the following lines to the .xaml page definitions does not help...

NavigationCacheMode="Required" 

NavigationCacheMode="Enabled" 

2) Manually forcing garbage collection when switching pages does not help. So doing something like this makes no difference...

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    GC.Collect();
}

Does anyone know if there a solution to this, or is it a fundamental issue with UWP apps?

Churchless answered 14/4, 2018 at 13:9 Comment(6)
Can you actually get it to throw an out-of-memory exception? Because "hogging" memory is a .NET feature, simply because the garbage collector is lazy and does not see a need to "free" anything, if nobody else needs it. After all, whether your RAM is "used" or "free" is only important if you'd use it for something else.Glint
Fair point. I will check. Perhaps it's worth mentioning that I'm coming at this from years of developing managed code for WinForms/WPF programs, where an ever increasing memory footprint was usually an indication of a problem. Perhaps that is not necessarily the case with UWP.Churchless
use the memory profiler from VS2017 and look if you see details about the grow.Hairraising
Your code is causing a leak: the event handler for your DispatcherTimer is keeping a reference on page instance and therefore preventing it from being GC'ed.Gertiegertrud
Thanks for the replies. Stefan: If I add OnNavigatedFrom event code as follows: timer.Tick -= Timer_Tick; timer = null; App continues to leak memory. So is there a way to prevent this happening?Churchless
The other bug in your test code is that you keep navigating forward, which will create an infinite backstack of page instances which will naturally bloat the memory. After fixing that, your test project no longer grows in memory usage. I'll post a reply and share the fixed code.Gertiegertrud
G
7

In the repro code provided you keep navigating forward, which will create an infinite navigation backstack of page instances (check Frame.BackStack.Count). Since those instances are stored in memory the app's memory usage will naturally grow unbound.

If you change the code to navigate back to MainPage, and therefore keep the backstack depth at 2, the memory will not grow noticeably with repeated back and forth navigation.

EDIT However, if we observe this over a longer period of time there is a measurable increase of memory (1.5KB per navigation in my testing). This is a known leak in platform code that has not yet been addressed as of Windows 10 Update 1803.

The updated version of your test project is shared here:

Here is the code for NewPage.cs: https://1drv.ms/u/s!AovTwKUMywTNoYVFL7LzamkzwfuRfg

public sealed partial class NewPage : Page
{
    DispatcherTimer timer = new DispatcherTimer();

    public NewPage()
    {
        InitializeComponent();
        timer.Interval = TimeSpan.FromSeconds(.01);
        timer.Tick += Timer_Tick;
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        timer.Start();
        long managedMem = GC.GetTotalMemory(true);
        ulong totalMem = Windows.System.MemoryManager.AppMemoryUsage;
        System.Diagnostics.Debug.WriteLine(string.Format("Managed Memory: {0} / Total Memory: {1}", managedMem, totalMem));
    }

    private void Timer_Tick(object sender, object e)
    {
        timer.Stop();
        Frame.GoBack();
    }
}

If in your real app you observe a MB size leak after just a couple of navigation cycles then this is not due to above mentioned platform bug, but due to something else that needs to be investigated in the specific app, with the VS memory profiler for example. Often times leaks can be caused due to circular references or static event handlers in the app code. A good first step to debug these would be see if the page's finalizer gets hit as expected when forcing a GC. if not, use the VS memory profiling tools to identify what objects are being leaked and who is holding on to that. That data will help to pinpoint the root cause in the app code for that specific case. Typically those are due to circular references, or static event handlers not being unsubscribed. Happy to help more with this if you can share info from profiling the actual app.

Gertiegertrud answered 15/4, 2018 at 15:20 Comment(14)
Thanks for looking. I have added your code modifications to my test app. The Managed memory remains constant but the Total memory continues to increase over time and it never reduces. Running the modified test app for an hour and the reported total memory figure rises from approx 29000000 to 113000000. My complete test app project is here: link. If you could try this and leave it running, hopefully you will see the same behaviour. I agree the test app does not represent my real app but it does suffer from the same leakChurchless
I forgot to mention. The updated test project that you provided in your answer also leaks Total Memory on my systems. The only change I made to your project was to alter the 'Solution Platform' from ARM to x86. It also leaks when targeting x64. Thanks again for lookingChurchless
Thanks for clarifying, I do see a gradual memory growth over time that comes from the platform. I have searched our database and saw that this is a known bug that hasn't been fixed yet. I will link this feedback to the bug for additional customer evidence. I will also update my answer to reflect this.Gertiegertrud
The size of the leak from the platform in my testing (with release build on Win10 1803) appears to be about 1.5KB per navigation. So if we translate the repro to user-speed and assume a user doesn't navigate more often than every 5sec in average app usage, it means they need to be working for a full hour, navigating every 5sec, until they see a 1MB growth in memory usage. I am not saying this isn't important to fix, but if in your real app you are seeing MB size leaks after a couple of navigations then there are likely additional leaks contributing that will require additional investigation.Gertiegertrud
Use case: A non-interactive, information presentation system which will be expected to run permanently for many days, possibly weeks. And all the time performing regular, programmatic page navigation. So while I guess this is low priority for MS, for me (and probably only me) this is a big issue. At least I'm now back to re-designing. Thanks again for your time.Churchless
Thanks for the additional info - I'll add this to the bug to help with the prioritization.Gertiegertrud
A fix has been submitted that is available currently in Windows Insider builds (18327 and above). If you have a chance, it would be great if you can try your scenario and let us know if is resolved or if there are still remaining/other issues. Thx!Gertiegertrud
@StefanWickMSFT Could you take a look at this related thread if you do not mind? social.msdn.microsoft.com/Forums/en-US/…Conversable
@Conversable that thread doesn't look related at all. If the page instance stays in memory it's a different issue, likely a problem in the app code. Post your code otherwise we can only speculate.Gertiegertrud
Thank you for checking it. Let me see if I can come up with a simple repro.Conversable
@StefanWickMSFT Do I have to remove every event handler even including those specified in Xaml of the page for it to be cleared from memory like PointerEntered and PointerExited events of his one? imgur.com/FF2S20xConversable
@Conversable No. I recommend using the memory profiling tools to check what's holding on to your page instances.Gertiegertrud
Thank you. I found the cause: CoreWindow.GetForCurrentThread().KeyUp not unsubscribed. I use the memory snapshot of the debugger and drill down objects. Hope this is what you mean by memory profiling tools.Conversable
@Conversable Great - glad to hear you were able to fix the issue.Gertiegertrud
I
2

Yes. This is a bug in UWP. I opened a microsoft support ticket a few month ago and they said last week that they found the bug and solved them. They will ship the fix with the windows insider preview build with the next updates (so i think next week - on the 21.12.2018 it still wasn't included). the fix for everybody will be shipped with the spring update for windows 10.

Idiocrasy answered 27/12, 2018 at 16:36 Comment(0)
S
2

We made huge improvements in memory handeling and GC calls with latest UWP 6.2.9 Nuget update while targeting >=RS3 (Win 10 1709). The complete release notes are here

Selfridge answered 20/9, 2019 at 10:25 Comment(0)
M
0

I experienced similar behavior when navigating. but it was always on debug mode. My application even exceed (40Mb before navigations) 100Mb on debug mode when navigating. but NEVER exceeded 1Mb in final release product.

Mirthless answered 9/5, 2020 at 17:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.