question

danielescipioni avatar image
3 Votes"
danielescipioni asked LukeBlevins commented

Memory leak on UWP with target versions 10.0.17763.0, 10.0.18362.0 and 10.0.19041.0

In an UWP application when I set TargetPlatformVersion and TargetPlatformMinVersion to 10.0.17763.0, 10.0.18362.0 or 10.0.19041.0 (I keep them always aligned) I found that pages are not released from memory causing serious issues, as the application memory usage quickly grows up.

This GitHub repo contains the code that reproduce the problem https://github.com/DanieleScipioni/MemoryLeak, this a basic application with 2 pages. You can just go from the first page to the second, the second page contains a ListView of ListViews with an ItemClick event handler.

 <Page x:Class="MemoryLeak.Views.ListsPage">
     <Grid>
         <ListView IsItemClickEnabled="True"
                   CanReorderItems="False"
                   ItemsSource="{x:Bind _secondPageViewModel.List, Mode=OneWay}">
             <ListView.ItemsPanel>
                 <ItemsPanelTemplate>
                     <ItemsStackPanel Orientation="Horizontal" />
                 </ItemsPanelTemplate>
             </ListView.ItemsPanel>
             <ListView.ItemTemplate>
                 <DataTemplate x:DataType="local:StringList">
                     <ListView ItemsSource="{x:Bind}"
                               ItemClick="ListView_OnItemClick"
                               IsItemClickEnabled="True"/>
                 </DataTemplate>
             </ListView.ItemTemplate>
         </ListView>
     </Grid>
 </Page>

To see the memory leak is enough to go forward and back from the 2 pages, and see memory increasing on the Task Managed, anyway I used Visual Studio Diagnostic Tool.

With Visual Studio Diagnostic Tool I execute this test: open app, go to next page, go back and take a snapshot, again go to next page, go back and take a another snapshot, then stop data collection and compare the 2 snapshots. GC is started several time before of each snapshot.

With TargetPlatformVersion 10.0.19041.0, there are 205 more objects in the second snapshot

29103-2004-with-eventhandler-overview.png

and looking at count diff and at paths to root I see this

29151-2004-with-eventhandler-paths-to-root.png

So at the end ListsPage is not released from memory.

The behavior must be that both count and count diff is 0, meaning that ListsPage is released from memory.

windows-uwp
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

RichardZhang-MSFT avatar image
0 Votes"
RichardZhang-MSFT answered LukeBlevins commented

Hello,

Welcome to Microsoft Q&A.

When we use Frame.Navigate(typeof(SomePage)), by default, a new instance is generated based on the type of this Page. So multiple navigations actually creates multiple instances.

When the page is navigated, the previous page will not be released, because for the Frame, a history record is required, which will retain a reference to the page

If we want to limit this situation, we can cache the page:

 private bool _isLoaded = false;
 public ListsPage()
 {
     _secondPageViewModel = new SecondPageViewModel();
     NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled;
     InitializeComponent();
     Loaded += async (sender, args) =>
     {
         if (!_isLoaded)
         {
             _isLoaded = true;
             await _secondPageViewModel.Init();
         }
     };
 }

After the page is cached, navigate to the page again, and Frame will use the cache first. This means that no new page instances will be created, and the memory usage will not increase.



Update

In your question, you did not use Frame for page navigation (sorry, this is my oversight).

Even so, caching pages is still a solution to the problem.

Simply put, every time you call App.NavigateTo(Type), you recreate the instance of a page based on this Type.

Every time a ListsPage is created, a List<StringList> will be regenerated, and the memory overhead is mainly used to create the corresponding ListView.

So as an alternative to caching, we can create a FrameworkElement list. When the page is first created, the page will be stored in the list. The next time we navigate, we can check the list and take out our previous cache instead of creating a new one.

private List<FrameworkElement> _pageList;

public App()
{
    _pageStack = new Stack<Type>();
    _pageList = new List<FrameworkElement>();
    InitializeComponent();
}

//...

private void ChangeWindowContent(Type type)
{
    if (_pageList.Any(p => p.GetType().Equals(type)))
        Window.Current.Content = _pageList.Where(p => p.GetType().Equals(type)).First();
    else
    {
        var page= (FrameworkElement)Activator.CreateInstance(type);
        _pageList.Add(page);
        Window.Current.Content = page;
    }
    

    SystemNavigationManager systemNavigationManager = SystemNavigationManager.GetForCurrentView();
    systemNavigationManager.AppViewBackButtonVisibility = CanGoBack
        ? AppViewBackButtonVisibility.Visible
        : AppViewBackButtonVisibility.Disabled;

}


In this way, by calling the previous cache, we can avoid the additional overhead caused by repeatedly creating instances.

In ListsPage, please use the previous reply to modify the Loaded event to avoid repeated creation of List<StringList>

Thanks.


If the response is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

· 7
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi @RichardZhang-MSFT, your answer does not take in consideration important details of the how to reproduce steps:

  • please check at the code referenced in the question: I do not use a Frame, to simplify the example when the page is changed, I just change Window.Current.Content.

  • I create snapshots when the app is in main page, reached with a back navigation, so there isn't any navigation stack that can reference pages

Please note

  • that exactly the same code has not the same issue on version 10.0.17134.0: ListsPage instances are garbage collected

  • that if you remove ListView_OnItemClick handler ListsPage instances are garbage collected, so there is something wrong related to events RefCount as shown in the image

Moreover, NavigationCacheMode does not works as you say: default value is Disabled that means the Page is not cached at all also when the Frame has a navigation back stack, the back stack just has a reference to the type of the page.


0 Votes 0 ·

Sorry, this is my negligence, I have updated the answer, please check.

0 Votes 0 ·

I checked updated answer and this is not a solution.

I do not want to avoid to create new page instances for the same page type, creation of new pages is not a problem at all, if they are correctly garbage collected as it happens in 10.0.17134.0.

The problem here is that if the page is not shown anymore, it is not garbage collected. Also with your workaround if a page is not garbage collected and the app has many page types and you move from one page type to another page type, the app memory keep growing till the limit.

Please lets concentrate to the memory leak problem, there is a memory leak.




1 Vote 1 ·
Show more comments
CliffordAgius avatar image
0 Votes"
CliffordAgius answered

Hi All,

I am seeing the same issue but from a slightly different angle, I am using a Xamarin.Forms project that has a UWP target and in this project the Xamarin CollectionView builds on the UWP ListView so when I update the Binding Object of the ListView as needed for the project every 5 seconds there is a massive memory leak.

SO this isn't a page navigation/build issue it's in the way that the ListView is building the ViewCell as it looks like it is not reusing but creating new each time and thus the resources are not release.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.