Re: memory leak



Hey all,

First off, thank you. I no longer consider this a bug, rather, I
consider it a caveat that any .NET CF developer using Bitmaps must be
aware of.

Chris, as you suggested in your blog, I spent a bit of time massaging
the code listed to see how the memory responds. I used the .NET
Compact Framework RPM of CF 2.0 SP1 & perfmon to display a running
account of GC statistics. I also used the Win CE RPM v5.0 that comes
with EVC 4.0, the tool the .Net CF RPM seems to have replaced.

For the WinCeRPM(evc4.0) I displayed the following CE Memory
Statistics:
- Available Physical (scale = 0.000001)
- Memory Load (scale = 1.0)
- Total Physical (scale = 0.000001)

The main difference I witnessed was with the second example where the
Bitmap (int, int) constructor was used. Using the WinCE RPM(evc 4.0),
you'll quickly see the Available Physical Memory drop 27 - 32MB,
roughly equivalent to the max mem allowable for the process. I'd
expect the CLR to clean that up when the application exits, however,
that memory is "leaked" until the device is hard reset. This is what I
had been seeing occasionally with one app I have been working on.

While reading the response from the CF team concerning DIBs and DDBs, I
started to wonder what happens with an ImageList, more specifically,
what happens under the hood with a ImageList? When adding a bitmap to
an ImageList, is a reference to the original bitmap used or does a new
bitmap get created??? If a new bitmap is created, which constructor is
used???

After a bit of experimenting it seems that the ImageList creates a copy
of the original Image. Fortunately, it does not seem to use the
Bitmap(int, int) constructor. I was fearing for the worse given the
ImageSize property. Exactly what happens under the hood, only MS knows
for certain? What is certain is that all members of the ImageList
collection must also be disposed of since it appears new images are
created.

I took the liberty to modify some of the code from the blog to
determine if a reference or a copy of the Bitmap was used in the
ImageList. The following code snippet runs in a ThreadPool thread,
hence the raising events & etc.:

while (loop_run) {
try {
iterations++;
using (Bitmap b = new Bitmap(GetImageStream())) {

il_5.Images.Add(b);
}

RaiseUpdateListViewEvent(obj_since_failure++);

if (iterations % 100 == 0) {
Debug.WriteLine(string.Format("{0} objects
Created", iterations));
break;
}
} catch (OutOfMemoryException) {
Debug.WriteLine("Waiting for finalizers to
run...");
Debug.WriteLine(string.Format("{0} objects since
last failure", obj_since_failure));
obj_since_failure = 0;
ClearImageList(il_5);
GC.WaitForPendingFinalizers();
} catch (Exception) {
Debugger.Break();
loop_run = false;
}
}


In the example above, the ImageList il_5 is bound to the SmallImageList
of a ListView object. The RaiseUpdateListView method raises an event
to create a new ListViewItem and update the Listview. I figured, if
the ListView displays the Bitmap even after Bitmap b is disposed, then
the Image must have been copied somewhere. This is indeed what
happens.

In addition to this example, I also added the bitmaps created in each
example of ctacke's blog to separate ImageLists. With example 1 from
ctacke's blog, adding the Bitmap to a ImageList made the "ideal"
performance dissappear pretty quickly. Fortunately, all resources were
released when the application exits. All of the other examples behave
roughly in the same manner. Example number 4 will occasionally throw
an regular Exception when attempting to add the bitmap to the ImageList
after memory usage gets past a certain point.(Weird)

Anyhow, this ballyhoo has shed some light on what one I must do to
clean up my app which uses a fair amount of Bitmaps, ImageLists, etc.

Thank you all for the insight.

f(illiterate)


<ctacke/> wrote:
Inline...

1. Is it just my natural "Defensive Coding" nature or does anyone else
find this really really scarey? For example, it seems as though, since
MSFT have already said that they will fix this problem, that we're going
to have to check for and code for specific CF versions. Secondly,
performance is dependant on which constructor gets used?!? Thirdly,
exception handling and GC behavior is dependant on which constructor gets
used. So now we're left to do our own memory management and we have no
idea how our performance will/may take a hit when users change to a new
CF.

If you code defensively, you'll never hit this. If you use the
WaitForPendingFinalizers approach, then if it is fixed in a future version,
the exception won't get thrown and the wait will never occur, so in theory
perf will improve with no code modifications.

2. Chris, do you know if the information you posted is specific for CF 1,
CF 2, or both? (I have seen the same (incorrect) behavior on both)

I only tested with CF 2.0 SP1, so I can't say for certain. If I were to
guess I imagine it's been there all along.

3. When do they plan on fixing it? How do they plan on fixing it? How
will their fix impact an application's performance?

I don't work for Microsoft so I have no idea if or when this behavior may
get changed. I can't imagine it would impact perf in any way except the GC
would collect properly without you having to call Dispose. It might
actually be a little slower because it may add an additional check.

4. What other classes in CF (or Full) exhibit such odd (and incorrect)
behavior? Do the CF Team know of any that might jump up and bite us like
Bitmap did to me?

That's like asking what bugs are out there that are unknown. I don't
specifically know of any other behaviors like this, but then there's no
guarantee. The CF is heavily tested, but only through actual use and
feedback will more difficult to find problems get addressed.

5. Why did they do it like this?

I didn't architect it or have any part in coding it, so I have no idea. I
do know that the CF team is full of very good developers and architects, so
I'm sure there was some fundamental reason behind it. Whether is was "good"
or "bad" I can't say. I just may have done things slightly different if the
world were perfect and I got to run things.

6. When will I run out of video RAM? i.e. I'm assuming that I could
still have lots of 'free memory', yet a "new Bitmap (w, h)" call will fail
when video RAM is exhausted. This will throw an OOM exception (perhaps
ArgumentException on Full Framework) when I have a ton of virtual memory
left because it may be allocating memory in the 'dedicated video ram'.

What is your definition of "video RAM"? Many CE devices don't have a
separate graphics controller with its own RAM. Some that do still don't use
it for bus-architecture reasons. And if such hardware is present, the CF
has no idea that its there, so it's purely up to the driver if/when it
should allocate against it. Typically this is used for the display driver's
framebuffer(s)

You could try to allocate and have it throw if you're out of virtual memory.

This is more than an implementation bug, this is a fundamental design flaw
in the CF (and Full Framework which is fixed in its latest version) and
one which goes against the fundamental nature of VM/GC notion. Again, I
love what the CF Team have given us, but IMHO this is a serious failure of
the design, implementation, and testing of .NET at Microsoft.

Again, I see it as a simple implementation bug. When it OOMs on creation,
the runtime should collect, wait for finalizers and allocate again. Plain
and simple. It would then behave like a GC system is supposed to.


--
Chris Tacke
OpenNETCF Consulting
Managed Code in the Embedded World
www.opennetcf.com
--

.



Relevant Pages

  • Re: memory leak
    ... Adding GC.Collectcauses the memory to drop, ... GC.Collect in the exception handler, ... Now do you still agree that the GC will cleanup Bitmap objects? ... Dispose is not a required call - it is optional. ...
    (microsoft.public.dotnet.framework.compactframework)
  • Re: Convert Bitmap to byte* in C++
    ... CreateStreamOnHGlobal(NULL, true, &buffer); ... this creates an internal shared memory block of size 0. ... If we knew the type of 'bitmap', which is NOT an HBITMAP, or a CBitmap, it might be ... the nature of the exception. ...
    (microsoft.public.win32.programmer.gdi)
  • problem: 256 color bitmaps in ImageList in 256 color mode
    ... I have subclassed the common control toolbar and am loading 256 color ... that bitmap would be painted correctly with the newly realized palette. ... when the Toolbar paints its ImageLists: ...
    (microsoft.public.win32.programmer.ui)
  • Re: memory leak
    ... Chris, as you suggested in your blog, I spent a bit of time massaging ... For the WinCeRPMI displayed the following CE Memory ... Bitmap constructor was used. ... clean up my app which uses a fair amount of Bitmaps, ImageLists, etc. ...
    (microsoft.public.dotnet.framework.compactframework)
  • Re: Exception in creating Bitmap
    ... When you create a Bitmap from a jpg file it is uncompressed in memory and ... will require considerably more memory. ... > I have some codes as follows and it always return Exception even if the ... > Bitmap bmp = new Bitmap; ...
    (microsoft.public.dotnet.framework.compactframework)