For...Each Memory Usage

  • Thread starter Thread starter John Kiernander
  • Start date Start date
J

John Kiernander

I've just come across an interesting problem (?) in VB.NET. It seems
that a for..each loop prevents the garbage collector from being able
to clean up an object. Check out the two code sections below (if you
want to replicate this problem I suggest you use a pretty big data
table so that the memory footprint will be visible through task
manager):

Example 1:

For Each dtrThis In dtbThis.Rows
Next

dtrThis = Nothing
dtbThis.Dispose()
dtbThis = Nothing

GC.WaitForPendingFinalizers()
GC.Collect()

Example 2:

For intThis = 0 to dtbThis.Rows.Count - 1
Next

dtbThis.Dispose()
dtbThis = Nothing

GC.WaitForPendingFinalizers()
GC.Collect()


I (perhaps incorrectly) assumed that these two cases would behave in
the same way. However upon collection, example 2 frees a large
datatable sized chunk of memory, whereas in example 1 the collection
doesn't seem to free anything.

There is obviously a logical explanation for this behaviour, but it
escapes me.

Any thoughts?

John
 
Not sure if this is the issue but...

In example one you set the variable to nothing before you call dispose. The
variable does not reference the object by the time you call dispose.

In example two you call dispose before setting the variable to nothing.


--
Mike

Mike McIntyre
Visual Basic MVP
www.getdotnetcode.com
 
Thanks for the response Mike.

I don't think the problem is related to the assignment you point out.

In the first example I am setting the datarow (dtrThis) to nothing, not
the datatable (dtbThis). I can understand the confusion, for some
reason I decided to make my variable names ridiculously similar.

The code runs without any exceptions in both cases, it is simply the
persisting memory which confuses me.
 
Well, a functional equivalent to your Example 1 is:

IEnumerator e = dtbThis.Rows.GetEnumerator();

while (e.MoveNext())
dtrThis = (whatever type) e.Current;

(or whatever the VB syntax is...)
..
..
..

while in the second one you just do a for i = 0 to const loop, which is just
like nothing.

The MoveNext() and get_Current() methods might do anything that, maybe
temporarily, references the dtbThis object, or they allocate some more
memory that can't be collected immediately. Another thing is, that I'm not
sure you're calling the collection functions in the right order, as I guess
that the WaitForPendingFinalizers() should be issued AFTER GC.Collect().
Anyway, you should never call any of these functions, unless you have a
really good reason to do so.

As for setting the variables to null, it doesn't do much difference (unless
they're global variables - not clear from your code), because the compiler
keeps track of unused references and makes them eligible for collection
anyway.

Hope this helps,
Stefan
 
Thanks for that Stefan.

Your point that the 2nd loop (For i = 0 to...Count) is not comparable to
the first, is totally valid. I stripped out all the code in the loop to
save space. The actual scenario which I started with was quite complex
but in the first scenario operations were performed on the datarow
(dtrThis) whilst the second scenario had all the same operations
performed on dtbThis.Rows(intThis). My understanding was that dtrThis
was simply a pointer to the same row so having set this pointer to
nothing the disposal of the datatable should behave in exactly the same
way.

Again I agree that GC.Collect calls should be avoided but I am
performing operations on a large datatable and it is a requirement of
the application that the memory footprint is kept to a minimum.

As for the issue of when to call the GC.WaitForPendingFinalizers method,
I'm unsure. My understanding was that on calling the dispose method the
finalization would be run in a separate thread. It would therefore be
necessary to wait for this thread to complete before attempting a
collection. Having read around the subject a little more I'm not sure
that my assumption was correct.
 
Another thing that comes to my mind is whether the foreach loop doesn't
create some temporary variable that is not detected by the GC as unneeded
and thus a reference is kept during the GC.Collect() call... It would be
best to check the compiled code. To do this, I suggest using Lutz Roeder's
..NET Reflector

http://www.aisto.com/roeder/dotnet/

Anyway, is the memory in the first example released later?
 
Back
Top