Cannot find the memory leak

2020-01-28 09:19发布

问题:

I have been working on a WP7 app, it's image gallery app, with basic zooming and flick gestures implemented.

For test purposes I compiled the app with offline images(their filenames are numbered) set to Content and accessed them via hard coded string (which will be replaced later).

But came to realize that app consumes a lot of memory. I thought it was due to images and found this blog; images were always caching. I used the code from the blog to rectify this. Still memory is not released, although rate of consumption did go down.

For final attempt I created another test app with basic feature 2 button for navigation and image control for images, just to make sure it was not my gesture codes that could be the problem.

This is the xaml

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Image Grid.Row="0" x:Name="ImageHolder" Height="Auto" Width="Auto" Stretch="Uniform" Tap="image_Tap" />
    <TextBlock x:Name="MemUsage" />
    <StackPanel Grid.Row="1" Orientation="Horizontal">
        <Button x:Name="PrevButton" Content="Prev" Width="240" Click="btnPrev_Click"/>
        <Button x:Name="NextButton" Content="Next" Width="240" Click="btnNext_Click"/>
    </StackPanel>
</Grid>

This is the .cs file

    const int PAGE_COUNT = 42;
    int pageNum = 0;
    public MainPage()
    {
        InitializeComponent();
        RefreshImage();
    }

    private void btnPrev_Click(object sender, RoutedEventArgs e)
    {
        pageNum = (PAGE_COUNT + pageNum - 1) % PAGE_COUNT; // cycle to prev image
        RefreshImage();
    }

    private void btnNext_Click(object sender, RoutedEventArgs e)
    {
        pageNum = (PAGE_COUNT + pageNum + 1) % PAGE_COUNT; // cycle to next image
        RefreshImage();
    }

    private void image_Tap(object sender, GestureEventArgs e)
    {
        RefreshTextData();
    }

    private void RefreshImage()
    {
        BitmapImage image = ImageHolder.Source as BitmapImage;
        ImageHolder.Source = null;
        if (image != null)
        {
            image.UriSource = null;
            image = null;
        }
        ImageHolder.Source = new BitmapImage(new Uri("000\\image" + (pageNum + 1).ToString("D3") + ".jpg", UriKind.Relative));
        RefreshTextData();
    }

    private void RefreshTextData()
    {
        MemUsage.Text = "Device Total Memory = " + (long)DeviceExtendedProperties.GetValue("DeviceTotalMemory") / (1024 * 1024)
            + "\nCurrent Memory Usage = " + (long)DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage") / (1024 * 1024)
            + "\nPeak Memory Usage = " + (long)DeviceExtendedProperties.GetValue("ApplicationPeakMemoryUsage") / (1024 * 1024);
    }

But still memory leak is there and I can't pin point it. I am having a hard time finding it. Memory profiler shows that I have many instances of a string, and I can't interpret that.

Few Points:

  • I have images in a folder "000" and named "image###". At present I have images with file names from "image001" to "image042"
  • Test app has a memory footprint of 6 MB as soon as it shows the first page completely with the image, and after fisrt page change it rises to almost 18-20 MB
  • Subsequent page change result in gradual increase in memory and then eventual crash, if number of images permit, otherwise after cycling through all images memory consumption is constant
  • I am using .jpg files with approx dimension 1280 x 2000, for testing I am not resizing images.

回答1:

I have the same kind of app, with the next/previous picture buttons. And I had exactly the same memory leak, which has driven me mad.

I still haven't been able to find the root cause, but I've managed to bypass it with a ugly hack. When displaying the next picture, I force the old image source to load an invalid picture, thus freeing the memory. I don't understand why removing all references and calling the garbage collector isn't enough, there must be another reference kept internally somewhere.

Anyway, here is the hack:

private void DisposeImage(BitmapImage image)
{
    if (image != null)
    {
        try
        {
            using (var ms = new MemoryStream(new byte[] { 0x0 }))
            {
                image.SetSource(ms);
            }
        }
        catch (Exception)
        {
        }
    }
}

You can call it for instance in your RefreshImage method:

private void RefreshImage()
{
    BitmapImage image = ImageHolder.Source as BitmapImage;
    ImageHolder.Source = null;

    DisposeImage(image);

    ImageHolder.Source = new BitmapImage(new Uri("000\\image" + (pageNum + 1).ToString("D3") + ".jpg", UriKind.Relative));
    RefreshTextData();
}

Kinda ashamed to use that, but at least it seems to work.



回答2:

I have tried your code sample, but in Windows Phone 8 environment and I couldn't reproduce the leak. The only difference is that I've used my own images.

The current memory usage stayed at 13MB for my 512 WVGA Emulator and the Peak stayed at 14MB. I have pushed the "next button" around 20 times.

Also have you tried using Bindings for ImageHolder instead of setting the Source manually?

(btw, visually I don't see any possible memory leaks in your codebehind).

(Also check this article http://blogs.windows.com/windows_phone/b/wpdev/archive/2012/02/01/memory-profiling-for-application-performance.aspx )



回答3:

After many trial runs and debugging sessions I found that this caching of images is not performed (or not performed as aggressively) when images are residing in the app's IsolatedStorage.

Thing is I was using the images which were a part of the xap file, included as content. I did this because I just wanted to test my image viewer. But this would not be the case when my app would finish. App was really designed to store images in Isolated storage and display them.

So I setup the necessary code and voila, Images are now getting garbage collected, even though they still got cached. See image below(see how many times Garbage Collector gets called). This is a solution to a not so trivial question, that's why no one else faced problem of this kind.

I believe that when WP7 silverlight finds that images is not from Isolated storage it assumes image is from some remote URI and decides to cache it anyway. And that's where silverlight image caching problem kicks in. As another answer confirms this doesn't happens in WP8.



回答4:

Try this approach: Image downloader with auto memory cleaning. It is advanced KooKiZ's sample, which supports visualization. Sample project is here: https://simca.codeplex.com/