6

I have a simple wpf application. In main window i have stack panel and 2 buttons. First button adds 100 my user controls (without any data bindings, events, bitmaps), and second removes all of them from panel and calls GC.Collect(). And there are some problems: 1. After i clicked "remove" button first time not all my memory releases, and I must click it few times to release more memory. 2. After 5 - 10 min memory releases but few megabytes dont.

for example after my app starts it takes ~22mb when i adding 500 controls - ~60mb after i clicked "remove" button first time - ~55mb (I wait some time, memory not deallocated) i click few times and memory fell down to 25mb, I dont understand this, I am new in WPF, and maybe i miss something I want to release memory immediately.

<Window x:Class="WpfApplication10.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="385" Width="553">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="25" />
        <RowDefinition Height="240*" />
        <RowDefinition Height="25" />
    </Grid.RowDefinitions>
    <Grid 
            Name="border1" 
            Grid.Row="1"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch" >
        <ScrollViewer VerticalAlignment="Stretch"
                      Name="scrollViewer1" 
                      HorizontalAlignment="Stretch">
            <StackPanel 
                Margin="3,3,3,3"
                Background="Transparent"
                VerticalAlignment="Stretch"
                Name="activityStackPanel"
                HorizontalAlignment="Stretch">
            </StackPanel>
        </ScrollViewer>
    </Grid>
    <Button Content="Button" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="12,0,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
    <Button Content="Button" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="141,0,0,0" Name="button2" VerticalAlignment="Top" Width="75" Click="button2_Click" />
    <Label Content="Label" Grid.RowSpan="2" Height="28" HorizontalAlignment="Left" Margin="34,0,0,0" Name="label1" VerticalAlignment="Top" />
</Grid>

namespace WpfApplication10
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            int N = 100;
            //var r = new ActivityStatisticItem("111", "222", DateTime.Now, "333", 1);
            for (int i = 0; i < N; i++)
            {
                activityStackPanel.Children.Add(new UserControl1());
            }

            label1.Content = activityStackPanel.Children.Count;
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
           activityStackPanel.Children.Clear();

            label1.Content = activityStackPanel.Children.Count;

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    }
}
<UserControl x:Class="WpfApplication10.UserControl1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         Background="Transparent"
         Margin="0,2,0,2"
         MinHeight="80"
         MinWidth="130"
         MaxHeight="80">
<Grid Width="441">
    <Grid.RowDefinitions>
        <RowDefinition Height="40" Name="rowTop" />
        <RowDefinition Height="40" Name="rowBottom"/>
    </Grid.RowDefinitions>
    <Border BorderBrush="Gray" 
            BorderThickness="1" 
            HorizontalAlignment="Stretch" 
            Background="LightGreen"
            Name="contactPanel" 
            CornerRadius="3,3,3,3"
            VerticalAlignment="Stretch" Panel.ZIndex="1" >
        <Grid
            VerticalAlignment="Stretch" 
            Name="grid1" 
            Margin="3,0,3,0"
            HorizontalAlignment="Stretch">

            <Label Content="Contact" Height="15" HorizontalAlignment="Left" Margin="15,3,0,0" Name="headerLabel" Padding="0" VerticalAlignment="Top" FontSize="10" FontWeight="DemiBold"/>
            <Label Content="00/00/0000 00:00:00" Height="15" HorizontalAlignment="Left" Margin="13,18,0,0" Name="timeLabel" Padding="0" VerticalAlignment="Top" FontSize="10" Width="100" FontWeight="DemiBold" />
            <Label Content="00:00:00" Height="15" HorizontalAlignment="Right" Margin="0,18,0,0" Name="durationLabel" Padding="0" VerticalAlignment="Top" FontSize="10" Width="38" FontWeight="DemiBold"/>

            <!--<Image Height="12" HorizontalAlignment="Left" Margin="0,3,0,0" Name="directionPictureBox" Stretch="Fill" VerticalAlignment="Top" Width="12"  />
            <Image Height="12" HorizontalAlignment="Right" Margin="0,20,41,0" Name="timerImage" Stretch="Fill" VerticalAlignment="Top" Width="12"          />
            <Image Height="12" HorizontalAlignment="Left" Margin="0,20,0,0" Name="dateTimeImage" Stretch="Fill" VerticalAlignment="Top" Width="12"       />-->


        </Grid>
    </Border>
    <Border BorderBrush="Gray" 
            BorderThickness="1,0,1,1" 
            Grid.Row="1" 
            Background="White"
            HorizontalAlignment="Stretch" 
            Margin="10,0,10,0" 
            Name="detailsPanel" 
            CornerRadius="0,0,3,3"
            VerticalAlignment="Stretch">
        <Grid HorizontalAlignment="Stretch" 
              Name="grid2" 
              Margin="3,0,3,0"
              VerticalAlignment="Stretch">
            <Label Content="Label" Height="15" HorizontalAlignment="Stretch" FontSize="9" Padding="0" Margin="0,3,0,0" Name="numberRadLabel" VerticalAlignment="Top" />
            <Label Content="Label" Height="15" HorizontalAlignment="Stretch" FontSize="9"  Padding="0" Margin="0,18,0,0" Name="queueRadLabel" VerticalAlignment="Top" />

        </Grid>
    </Border>
</Grid>

In user control i have only

         public UserControl1()
         {
            InitializeComponent();
         }
H.B.
  • 166,899
  • 29
  • 327
  • 400
Andriy Khrystyanovich
  • 1,422
  • 3
  • 19
  • 38
  • How are you measuring "memory usage"? In Windows Task Manager? – spender Jul 01 '11 at 12:11
  • 1
    What @Alistad means is: Can you provide code of a *minimal* working example that reproduces the problem? – Heinzi Jul 01 '11 at 12:12
  • Yes, that was meant to be **jocular and not offensive**. Please copy us some code, so that we can help. Thanks @hHeinzi – Aliostad Jul 01 '11 at 12:13
  • @Aliostad: Sorry, I did not want imply that your request was offensive -- on the contrary. I just wanted to add that he should provide a minimal example instead of just dumping his complete source here. ;-) – Heinzi Jul 01 '11 at 12:14
  • @Henizi No I just felt I might have gone too far in the tone. Anyway, I think it is fine now. – Aliostad Jul 01 '11 at 12:15
  • Where's your call to GC.GetTotalMemory(true)? I don't see how you are making any kind of empirical measurement here. – spender Jul 01 '11 at 12:19
  • I use process explorer and some memory profilers, and I saw that private bytes and working set doesnt release all memory, also i use windows task manager and it shows the same information as in process explorer (working set private) – Andriy Khrystyanovich Jul 01 '11 at 12:21
  • I also try to use ANTS memory profiler and .net memory profiler. But i cant resolve the problem. – Andriy Khrystyanovich Jul 01 '11 at 12:27

6 Answers6

11

I want to release memory immediately.

Don't. Trust GC.

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Don't. Trust GC.

After 5 - 10 min memory releases

Didn't I say trust GC?


  • Garbage collection model will make sure the unwanted managed memory in your system is released (which includes almost all of your controls memory). It uses an algorithm for optimising which includes generations, free memory available, possibly CPU available... so GC.Collect() will interfere with it.

  • GC.Collect() is asynchronous so no immediate effect.

  • The only resource you need to be careful is the unmanaged resource which usually is handled by Dispose Pattern. Otherwise don't mess with GC, it does its job very well.

Aliostad
  • 80,612
  • 21
  • 160
  • 208
  • But i didnt use unmanaged resources(if i dont mistake unmanaged resources are native resources handlers, file descriptors etc). If GC didnt help what i can use. In our app this controls will be added and removed many times, and if this is memory leak i have problem , after some time app will take too many memory and fell down( – Andriy Khrystyanovich Jul 01 '11 at 12:44
  • Yes, that is why you do not need to worry about it. I just mentioned it for the completeness sake. – Aliostad Jul 01 '11 at 12:45
  • "Not worrying about it," although usually true in garbage collected environments, feels a bit hand-wavy here. If you have a window which is leaking (and there are many ways that can happen, e.g. not deregistering a subscribed event), the method andronz posted is a valid, albeit hacky, way of detecting that. My assumption is that the code he posted up was purely of a testing nature. – DavidN Jul 01 '11 at 16:17
4
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

This is a surefire way of forcing non-GCable objects into Gen2 prematurely, thus increasing your memory footprint for a longer period of time, for no good reason.

As Aliostad said: don't!

Dan Puzey
  • 33,626
  • 4
  • 73
  • 96
3

Leave the garbage collector alone and let it do its job.

What you're describing isn't a memory leak. It's dynamic memory not getting released at the moment you think it ought to be released.

Are you the garbage collector? You are not. Worrying about when garbage gets collected isn't your job. If these objects are actually garbage - and they are - the memory will be there when you need it.

Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • "Are you the garbage collector? You are not..." I love this and will be using it :D – Andy Oct 22 '13 at 09:03
1

In a garbage collected environment, releasing memory immediately doesn't really make sense.

Since the CLR JITs code on demand, the first time you run your test you shouldn't see memory drop back to where it was initially. This makes sense because new code paths have been followed and code has been JITted. That code needs to reside somewhere in memory no?

Therefore, after your first test run, you shouldn't be able to collect back down to your initial memory footprint. Your baseline should be the memory usage you get after running the test once, not before. After running a second time, I am able to get memory back down to the baseline with a number of collections.

Also, I'd recommend running your project in release mode with no debugger attached. Running your program with a debugger attached will not show you a true memory profile, as there are various tricks it employs to keep objects around (e.g. Collect objects still in scope - GC.Collect).

This is all a moot point, however, because like I said above, reclaiming memory immediately doesn't make much sense in a GC environment (in most cases).

Community
  • 1
  • 1
DavidN
  • 5,117
  • 2
  • 20
  • 15
0

By using this Dll Invoke we can realocate the memory resources

public class MemoryManagement
{
[DllImportAttribute("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet =
CharSet.Ansi, SetLastError = true)]

private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int
maximumWorkingSetSize);

public static void FlushMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT) { SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}
Justin CI
  • 2,693
  • 1
  • 16
  • 34
0

I would concur with @Aliostad re GC. I does its job very well, BUT it is not a tool to magically clear all of your memory.

If you have memory leak problems, the most straightforward and reliable solution is to use a profiler, whih should be able to identify if you have a genuine leak and where it is. I have used Ants from Red Gate, but others may have better suggestions.

As well as following the usual guidelines, like making sure you properly dispose of stuff. Calling GC and hoping that it will work is not an alternative for proper code assessment.

Schroedingers Cat
  • 3,099
  • 1
  • 15
  • 33