16
votes

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?

4
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.nvoigt
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.NigelP
use the memory profiler from VS2017 and look if you see details about the grow.magicandre1981
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.Stefan Wick MSFT
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?NigelP

4 Answers

7
votes

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.

2
votes

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.

2
votes

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

0
votes

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.