Automatic garbage collection

A

Alain Dekker

Hi,

I know the automatic garbage collection is supposed to be "better" but
having spent my programming life looking after memory myself, I don't like
the fluffiness of it. OK, rant over.

I display a startup form, then overlay another "main run screen" when all
startup actions are complete. As part of that startup I display a bunch of
pretty images, changing on a timer tick event, in the startup screen.

I've cached those images (they're embedded into the EXE) into an array and
then cycle through the images as the startup goes on. Problem is that when I
overlay the main run screen, the memory from these images is never released.

Is there any way I can force C# to give me back the memory? There doesn't
seem to be any "delete" keyword like there is in C++ and its not going to be
simple to convert the code into a "using" clause.

Thanks,
Alain
 
M

Marcel Müller

Hallo,

I display a startup form, then overlay another "main run screen" when all
startup actions are complete. As part of that startup I display a bunch of
pretty images, changing on a timer tick event, in the startup screen.

I've cached those images (they're embedded into the EXE) into an array and
then cycle through the images as the startup goes on. Problem is that when I
overlay the main run screen, the memory from these images is never released.

if you are sure with "never", then your program is buggy. I.e. you
accidentally hold a strong reference to the image objects.

As soon as your clear your cache and do not hold any strong reference to
the images, the items are subject to be discarded by the GC. However, it
might not happen at the time you expect it. The GC has to be triggered
first. More specifically the generation 2 collector has to be invoked,
since cached objects often propagated to generation 2. Depending on the
kind of GC you use, this might not happen unless the memory is required
for something else. The recent .NET runtime has AFAIK 3 different GC
implementations.
Is there any way I can force C# to give me back the memory?

Yes you can force the CG to run. But it is strongly recommended NOT to
do so. You might easily confuse the GC, resulting in poor performance.
The most critical point is if you invoke the generation 0/1 GC too
frequently. This will force temporary objects to generation 2.
There doesn't
seem to be any "delete" keyword like there is in C++ and its not going to be
simple to convert the code into a "using" clause.

using won't help very much unless you have external resources like open
files, database connections, COM objects or something like that. using
only call Dispose at the end of the block. It does not free any memory.


Marcel
 
A

Arne Vajhøj

I know the automatic garbage collection is supposed to be "better" but
having spent my programming life looking after memory myself, I don't like
the fluffiness of it. OK, rant over.

It saves you some work.

So unless you have strong real time requirements it is a convenience.
I display a startup form, then overlay another "main run screen" when all
startup actions are complete. As part of that startup I display a bunch of
pretty images, changing on a timer tick event, in the startup screen.

I've cached those images (they're embedded into the EXE) into an array and
then cycle through the images as the startup goes on. Problem is that when I
overlay the main run screen, the memory from these images is never released.

Is there any way I can force C# to give me back the memory? There doesn't
seem to be any "delete" keyword like there is in C++ and its not going to be
simple to convert the code into a "using" clause.

First a question: what are you measuring? Are you measuring how much
memory your app has allocated from .NET or how much memory .NET has
allocated from Windows (including unmanaged memory allocated by
your app)?

If it is the second then you do not have a problem. The memory is
available to your app.

If it is the first then you need to figure out where that memory is
going.

One reason could be unmanaged resources used by your app and
not properly disposed.

As an example System.Drawing.Image implements IDispose so
you need to call Dispose either explicit or implicit via
the using statement.

Another reason could be that you still have a reference
to the objects. Example: if you keep the array you mention
above around, then the objects will not be GC'ed.

Arne
 
A

Arne Vajhøj

if you are sure with "never", then your program is buggy. I.e. you
accidentally hold a strong reference to the image objects.

As soon as your clear your cache and do not hold any strong reference to
the images, the items are subject to be discarded by the GC. However, it
might not happen at the time you expect it. The GC has to be triggered
first. More specifically the generation 2 collector has to be invoked,
since cached objects often propagated to generation 2. Depending on the
kind of GC you use, this might not happen unless the memory is required
for something else. The recent .NET runtime has AFAIK 3 different GC
implementations.


Yes you can force the CG to run. But it is strongly recommended NOT to
do so. You might easily confuse the GC, resulting in poor performance.
The most critical point is if you invoke the generation 0/1 GC too
frequently. This will force temporary objects to generation 2.


using won't help very much unless you have external resources like open
files, database connections, COM objects or something like that. using
only call Dispose at the end of the block. It does not free any memory.

It should free all unmanaged memory allocated.

Arne
 
A

Arne Vajhøj

"Fluffiness" being a well-know technical term, of course. :)

GC isn't uniformly "better" or "worse". It's "different". However, it
_does_ address a particularly common type of programmer error, with little
or no performance cost (and in fact in some cases can perform better).

Back in the 90's when .NET and C# were first showing up,

Very early adapter?

(1.0 was released GA in early 2002 and the first public beta went out in
mid 2000)
GC was my primary
objection to .NET. But back then, GC-based systems were slow and annoying.

Really.

I do not remember GC problems in 1.x.

Arne
 
A

Alain Dekker

I've been measuring rather crudely using the "Commit Charge" metrics in Task
Manager. After caching the list of bitmaps displayed on the splash screen I
can see that the memory consumed by the system is X if I cache 10 images,
0.9X with 9 images, etc. The memory does not "return" until I fully close
the application, regardless of whether I call Dispose on the elements of the
array or even Dispose on the splash screen prior to showing the main run
screen. I really cannot see how the reference to the objects created in the
array might be non-zero.

In response to your and others helpful advice, I must admit to not fully
understanding how the GC works nor how and when I might have expected memory
to be returned. I had been under the impression that once I dropped the
reference count to zero (called Dispose on the array AND form) that I might
expect to see the memory drop after a few seconds. I now realise that the
situation is more complex than that.

Also following your notes, I checked out GC.Collect() and note that there is
general advise to not call that and just allow the GC to run in the
background and perform its tasks.

Quick question: I've read a note somewhere saying you should "Use the
performance counters to see how much processing time is spent in the GC".
How do you go about that.

Thanks again for all the advise...I'm learning a great deal about .NET and
its intricacies.
Alain
 
A

Alain Dekker

Hi Peter,

Thanks again for an informative post. I meant by "fluffiness", "not knowing
when it will release memory" - and you're right, my term is rather
unhelpful! :blush:)

You're also right that I was just going by some of the metrics displayed in
Task Manager. I now realise this is naive of me. The GC might only return
the memory when another memory allocation occurs. Since I'm running well
below the physical amount of RAM in the system, I really haven't tested this
very well!

Thanks again...this marks another notch in my (rapidly) improving
understanding of .NET and I appreciate yours and others patience in advising
this (otherwise experienced Win32) developer!

Regards,
Alain
 
A

Arne Vajhøj

I've been measuring rather crudely using the "Commit Charge" metrics in Task
Manager. After caching the list of bitmaps displayed on the splash screen I
can see that the memory consumed by the system is X if I cache 10 images,
0.9X with 9 images, etc. The memory does not "return" until I fully close
the application, regardless of whether I call Dispose on the elements of the
array or even Dispose on the splash screen prior to showing the main run
screen. I really cannot see how the reference to the objects created in the
array might be non-zero.

You should call Dispose on anything that implements IDisposable
(have a Dispose method).

And do not worry about reference counting. .NET GC does not
use reference counting. Instead it checks what objects are reachable.
So if the array is no longer reachable then the elements are
no longer reachable and the fact that the array still references
the elements does not matter.

Arne
 
A

Arne Vajhøj

In response to your and others helpful advice, I must admit to not fully
understanding how the GC works nor how and when I might have expected memory
to be returned. I had been under the impression that once I dropped the
reference count to zero (called Dispose on the array AND form) that I might
expect to see the memory drop after a few seconds. I now realise that the
situation is more complex than that.

It is very complex.

But to get an idea of the mechanisms you can try and
play with this little program:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace E
{
public class Program
{
public static void PrintMemoryStatus()
{
Console.WriteLine("Application has allocated {0} MB from
..NET", GC.GetTotalMemory(false)/1000000);
Console.WriteLine(".NET has allocated {0} MB from Windows",
Process.GetCurrentProcess().PrivateMemorySize64/1000000);
}
private const int N = 100;
public static void Main(string[] args)
{
PrintMemoryStatus();
Console.WriteLine("Let us allocate {0} MB of managed
memory", N);
byte[] b = new Byte[N * 1000000];
PrintMemoryStatus();
b[N/2] = 0; // make sure it is not GC'ed before
PrintMemoryStatus
Console.WriteLine("Let us make it available for GC");
b = null; // never do this in real code
GC.Collect(); // never do this in real code
PrintMemoryStatus();
Console.WriteLine("Let us allocate {0} MB of unmanaged
memory", N);
IntPtr buf = Marshal.AllocHGlobal(N * 1000000);
PrintMemoryStatus();
Console.WriteLine("Let us make it available for GC");
Marshal.FreeHGlobal(buf);
// no point in doing GC for unmanaged memory
PrintMemoryStatus();
}
}
}

Arne
 

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