Winforms and memory usage

D

deerchao

I'm developing a WinForms application. It slowly eats up memory, one
client reported that it took 200MB or more, and finnaly crashed. I
myself noticed it's common to use up 30MB memory, but if I minimize it
(all the Forms will Hide, only a NotifyIcon is shown at the System
Notification Area), the memory usage comes down to 8MB immediately.
After that, even if I show the Forms again, it uses only 8MB memory.
Do I have another way to programmly cut down the memory usage, other
than Hide/Show all the Forms manually?

Thanks!
 
P

Peter Duniho

deerchao said:
[...]
Do I have another way to programmly cut down the memory usage, other
than Hide/Show all the Forms manually?

Are you failing to properly dispose of unmanaged resources?

You aren't very specific about your program, unfortunately, but suffice
to say it's not normal behavior for .NET application, forms-based or
not, to gradually just keep consuming more and more memory until it crashes.

The two most common areas for a basic form application to have problems
is failing to dispose form instances that were shown and then closed,
and failing to dispose drawing objects used in custom paint handling code.

There are lots of other ways to do it wrong too, but those would be the
obvious things to look for.

Barring that, you should come up with a concise-but-complete sample of
code that reliably reproduces the problem. Post the code, along with a
clear description of what the user must do to reproduce the problem, and
perhaps some other ideas will surface.

"Concise" means you only post the absolute bare minimum of code required
to reproduce the problem. "Complete" means nothing else needs to be
added in order to get the code to compile and run.

Pete
 
D

deerchao

deerchao said:
[...]
Do I have another way to programmly cut down the memory usage, other
than Hide/Show all the Forms manually?

Are you failing to properly dispose of unmanaged resources?

You aren't very specific about your program, unfortunately, but suffice
to say it's not normal behavior for .NET application, forms-based or
not, to gradually just keep consuming more and more memory until it crashes.

The two most common areas for a basic form application to have problems
is failing to dispose form instances that were shown and then closed,
and failing to dispose drawing objects used in custom paint handling code.

There are lots of other ways to do it wrong too, but those would be the
obvious things to look for.

Barring that, you should come up with a concise-but-complete sample of
code that reliably reproduces the problem. Post the code, along with a
clear description of what the user must do to reproduce the problem, and
perhaps some other ideas will surface.

"Concise" means you only post the absolute bare minimum of code required
to reproduce the problem. "Complete" means nothing else needs to be
added in order to get the code to compile and run.

Pete

Thanks!
My application is using several open source components like DockPanel
Suite, SharpZipLib, System.Data.Sqlite and NLog. And there are 20+
forms, along with about 15,000 lines other code, so it's not that easy
to find out what is wrong. Maybe I need more tests to find out what's
wrong. But since I can manually cut down the memory usage, can I do it
the code way?
 
J

Jay

Do non-.net applications (eg VB6, MS VS6, C++ Builder, Delphi) have the same
"problem" of appearing to use a lot of memory?



It sounds like you have a legitimate memory leak. You'll need to track this
down.

It also sounds like you need to run this from time to time:
http://www.jdconley.com/blog/archive/2006/05/08/fakeouttheusertothinkwedontuseanymemory.aspx

There's alot of debate about that 2nd one, and you should give it some
though before using it, but if your users are whining that it takes too much
memory, that'll shut them up... :)
 
C

Chris Mullins [MVP - C#]

In my experience, .Net WinForms is the worst (best?) at doing an
impersonation of a bloated sow.

In general, Winforms does quite a bit of things very well, but conservation
of desktop memory isn't on that list.
 
C

Chris Alton [MSFT]

The best thing to do to see if you really are leaking objects is to get a
memory dump of your application at a time you think it is using an excess
of memory. Then check and see if there are a lot of objects still held
around in memory that shouldn't be there. You can use the Debugging Tools
for Windows to get that and the sos.dll extension that comes with .NET 2.0.
There are quite a few articles out on the web that can help you with this.

You can get the 32bit version from here:
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx

-------------------------------------
Chris Alton, Microsoft Corp.
SQL Server Developer Support Engineer
This posting is provided "AS IS" with no warranties, and confers no rights.
--------------------
From: "Chris Mullins [MVP - C#]" <[email protected]>
References: <[email protected]>
 
C

Chris Mullins [MVP - C#]

Having spent far, far too much time at this, I would recommend two products
over SOS:

- SciTech Memory Profiler is excellent. They have a 14 day free trial.
- RedGate ANTS Memory Profiler is also excellent. They also have a trial.

When I do seminars or mentoring on this, I always start with these products,
and only revert to Son of Strike and WinDbg as a very last resort. The
learning curve on SOS is brutal, and most devs won't ever get through it.

The learning curve for SciTech is about a day, and it's extremely powerfull.
Amazingly so.

The learning curve for ANTS is a few hours, but it's nowhere near as
powerfull as SciTech.

--
Chris Mullins

Chris Alton said:
The best thing to do to see if you really are leaking objects is to get a
memory dump of your application at a time you think it is using an excess
of memory. Then check and see if there are a lot of objects still held
around in memory that shouldn't be there. You can use the Debugging Tools
for Windows to get that and the sos.dll extension that comes with .NET
2.0.
There are quite a few articles out on the web that can help you with this.

You can get the 32bit version from here:
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx

-------------------------------------
Chris Alton, Microsoft Corp.
SQL Server Developer Support Engineer
This posting is provided "AS IS" with no warranties, and confers no
rights.
--------------------
From: "Chris Mullins [MVP - C#]" <[email protected]>
References: <[email protected]>
Subject: Re: Winforms and memory usage
Date: Fri, 12 Oct 2007 13:43:36 -0700
Lines: 32

In my experience, .Net WinForms is the worst (best?) at doing an
impersonation of a bloated sow.

In general, Winforms does quite a bit of things very well, but conservation
of desktop memory isn't on that list.

--
Chris Mullins

http://www.jdconley.com/blog/archive/2006/05/08/fakeouttheusertothinkwedontu
seanymemory.aspx
 
B

bob clegg

Hi,
Just a thought when profiling, allow sufficient time for the full
behaviour to be shown.
I went chasing a 'memory leak' where the Private bytes and Working Set
usage went up for several hours for no good reason I could see.
They finally levelled out then came down again. Then went up again
etc.
Took a couple of days of this wobbling behaviour before I accepted
that there was no leak.
Bob
 
C

Chris Alton [MSFT]

That would be the Garbage Collector at work. The garbage collector won't
run until it deems it necessary (or you force it in your code by calling
GC.Collect). So it is possible your .NET app can use up ~300MB of RAM but
in reality that amount is much lower and the garbage collector has not run
yet. If you wait long enough the garbage collector should kick off and drop
the memory usage of your app into the normal usage range.

-------------------------------------
Chris Alton, Microsoft Corp.
SQL Server Developer Support Engineer
This posting is provided "AS IS" with no warranties, and confers no rights.
--------------------
From: bob clegg <[email protected]>
Subject: Re: Winforms and memory usage
Date: Mon, 15 Oct 2007 23:13:48 +1300
Message-ID: <[email protected]>
References: <[email protected]>
<[email protected]>
<[email protected]>
<#[email protected]>
 
W

Willy Denoyette [MVP]

Chris Alton said:
That would be the Garbage Collector at work. The garbage collector won't
run until it deems it necessary (or you force it in your code by calling
GC.Collect). So it is possible your .NET app can use up ~300MB of RAM but
in reality that amount is much lower and the garbage collector has not run
yet. If you wait long enough the garbage collector should kick off and
drop
the memory usage of your app into the normal usage range.

Sorry to correct you, but this is not true, the GC kicks in when the gen0
threshold has been reached, this threshold varies with the version of the
CLR ,the version of the GC (server/workstation) the size of the L2 data
cache and the allocation frequency. In general less than a few of MB but
certainly much less than ~300MB. Just write a small program that creates a
bunch of objects and watch the Gen0 collection count with perfmon while it
runs.

Willy.
 
C

Chris Alton [MSFT]

Please read my post carefully before jumping to conclusions.

I stated below that it is "possible" your .NET app can use up to 300MB of
RAM (as viewed in task manager) not that 300MB is the kick in point for the
Garbage Collector to run. Try loading up multiple large DataSets with
around 50,000 rows on a button click and you'll certainly see the memory
usage of the app exceed 300MB.

-------------------------------------
Chris Alton, Microsoft Corp.
SQL Server Developer Support Engineer
This posting is provided "AS IS" with no warranties, and confers no rights.
--------------------
From: "Willy Denoyette [MVP]" <[email protected]>
References: <[email protected]>
<[email protected]>
<[email protected]>
<#[email protected]>
<[email protected]>
<[email protected]>
 
W

Willy Denoyette [MVP]

Chris Alton said:
Please read my post carefully before jumping to conclusions.

I stated below that it is "possible" your .NET app can use up to 300MB of
RAM (as viewed in task manager) not that 300MB is the kick in point for
the
Garbage Collector to run. Try loading up multiple large DataSets with
around 50,000 rows on a button click and you'll certainly see the memory
usage of the app exceed 300MB.


Hmm... let me explain my point. The CLR starts a GC run whenever the Gen0
threshold (a variable value depending on some heuristics like the GC type,
CPU L2 cache size and allocation pattern/frequency, in general a value less
than a couple of MB) has been reached, removing non-rooted objects (garbage)
from Gen0 while promoting all still referenced objects to Gen1. This
continuous until Gen1's threshold has been reached, in which case the GC
will remove the non-rooted objects from Gen1 and move all (still) rooted
Gen1 objects to Gen2.
Again this goes on until the Generational heap (Gen0,1 and2) fills-up
completely (32MB), this will trigger a full collection (Gen0,1, 2 and LOH).
When a full collection cannot free enough space to hold another Gen0
quantum, then the CLR will will allocate another memory segment from the
process space in order to extend the GC heap. This process goes on until all
process memory space is consumed (say all objects remain rooted). The
additional segments aren't automatically returned to the OS, the CLR will
only trim it's WS and return empty segments when explicitely requested for
by the OS.

True, a .NET application can use 300MB of RAM (even a lot more), but this
300MB will mostly account for rooted objects like in the sample you gave
with the 50.000 rows datasets, but this doesn't mean that the GC doesn't run
while creating the Dataset(s), right?
"Try with a simple program [1] that allocated a List with string
objects...." and watch the GC counters in Perfmon while you fill the List,
you'll see that the GC runs more often than you may have guessed, and please
don't run this from a SQL/CLR, the SQL host sets the Gen0 threshold to a
higher value than the shell host).


class Program
{
static void Main()
{
Console.ReadKey();
List<string> list = new List<string>(2000000);

for(int i = 0; i < list.Capacity; i++)
list.Add(String.Format("This is a lonooooooooooooooooooooooooooooooog
string {0}", i));
Console.WriteLine(list.Count);
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
}

When I run this on my box, the "GC 0 Collections" counter goes from 0 to
123.
Gen1 from 0 to 46 and Gen2 from 0 to 6. Private bytes goes from a couple of
MB to 318MB (your mileage may vary)
You see, the GC ran 175 times while (obviously) nothing was being collected.


Willy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top