Is there a memory leak when looping through a listview control with a For Next loop in VB.NET?
Do this to see what I mean:
1. Create a new VB.NET project with a single form.
2. Add a listview (details view) with two column headers to it.
3. Add a timer with an interval of 1000.
4. Add two buttons. Button1 will add 1000 items to the listview. Button2 will start the timer that kicks off the For Next loops that seem to cause the leak.
5. Place this code in the project in the appropriate places:
Dim A As Integer = 0
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Counter As Integer
Dim Itmx As ListViewItem
For Counter = 1 To 1000
Itmx = ListView1.Items.Add(Counter)
Itmx.SubItems.Add("0")
Next
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Timer1.Enabled = True
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Timer1.Enabled = False
Dim Counter As Integer
A = A + 1
For Counter = 0 To ListView1.Items.Count - 1
ListView1.Items(Counter).SubItems(1).Text = A
Next
Timer1.Enabled = True
End Sub
6. Build and run it.
7. Open Task Manager and take a look at the amount of memory being used for this process as the timer kicks off the For Next loops.
Now the really strange part:
If you minimize the application while it is running it will release almost ALL of the used memory being reported as used in Task Manager. However, it will begin to climb again immediately.
Short of programatically minimizing my applications every few minutes (can you imagine how annoying that would be to the users?) I can't figure out how to fix this.
I'm sure I'm not the only person that ever looped through a listview in VB.NET. How do you guys keep it from leaking?
When I look at your code it seems that you are increasing the size of the contence in your list view control with each second that goes by. That is why your memory is draining so fast.
start with a 1000
after a second you add 2000
and with each second after that the number keeps doubling.
If you would clear the contence of the list box the memory wouldn't be as significant.
Sorry. I wasn't very clear. Click Button1 once to add 1000 items to the listview. Next click Button2 once to start the timer. After that don't click anything. All the timer is doing is looping through the listview and changing the subitem to the value of A every second. It's not adding entries to the listview. You can even remove the A=A+1 line and the memory usage still climbs each time the loop executes. I've even removed the
A=A+1
and
ListView1.Items(Counter).SubItems(1).Text = A
lines and replaced it with an IF statement that only looks at the entrys in the listview without modifying them in any way.
If listview1.items(counter).text = "Blah" then
End if
Memory usage still climbs. I'm baffled...
Every time you create a variable, object, etc it grabs memory. Same thing happens when the timer itself fires. It seems that MS's implementation of many properties (such as the Text property you are assigning) also work exactly the same way as you would write code to do it ie. they use variables. Over time, this keeps pushing your memory usage up.
.NET uses a garbage collection memory management system to periodically clean up no-longer-referenced memory, but this can be delayed if your system's CPU is too busy. If your computer runs out of memory, it will then fire the garbage collection. This can tie up the CPU constantly, which might appear to be a freeze, but will eventually clear itself.
Since you can't explicitly destroy variables & objects like you could in VB6, you therefore can't explicitly release the memory consumed. You CAN, however, request that garbage collection be performed.
Take a look at my test solution (attached), which I built specifically to diagnose apparent memory leaks we were having on a major app.
Unpack it, open it, & run it. Make sure the Visual Studio IDE is displaying the Output window, so you can see what's going on.
Once per second, a message like this is output to the IDE's Output window:
13.8 Mb (2,285,560/2) 595,384 - 2,285,560
The first number is the total amount of memory being consumed by the app. The second (in brackets) is the current number of bytes being used by variables, objects, etc. Ignore the "/2", as it's not relevant to this discussion. The last 2 numbers are minimum and maximum values experienced, excluding the first 5 seconds, which are not representative of normal operation.
You'll notice that the memory keeps going up, for no apparent reason. This is because each call to the timer implicitly creates new variable instances that are not destroyed immediately.
Every now & again, you'll see the value go right back down. This means that automatic garbage collection has just occurred.
You can also force garbage collection by clicking the app's "normal" button. This type of cleanup is fairly quick, so is useful to do on a regular basis if your app is exceeding acceptable memory usage. We now do ours every 10 minutes, but only if we exceed a system-definable threshold (200Mb). Note which memory value we are basing our logic decisions on though - the first one is NOT appropriate, since it includes code in memory, which can't be cleaned up by the garbage collector.
The "wait" button forces a more complete garbage collection, but can appear to freeze your system (as discussed earlier).
Ultimately, the "minimum" value displayed should be roughly the same upon each collection. If it keeps going up over time, even after you've forced collection a few times manually, then you probably have found a memory leak.
I think you probably have nothing to worry about, but if you want to be sure, try putting some code to force garbage collection on demand into your own test harness, and see if you can achieve a (roughly) static minimum memory consumed.
Thank you. I'll check it out. I also read that the Task Manager doesn't report memory useage correctly when it comes to programs that are using the .NET Framework and this can lead to incorrect memory leak assumptions. I hope they fix that one day as it's the only easy way I know of to check a program's memory usage and most people balk at a program that appears to be eating up more than it's share (i.e. end-users). I'd hate to think some people would frown on my programs due to a "bug" in the Task Manager that made them appear to be hogging all the memory when they really aren't.
You're right that Task Manager doesn't report memory exactly the way we'd like it to, but it still IS a valid measure of how much memory the OS has currently allocated to an app. It would be nice if it separately listed the memory which could be (but hasn't yet been) garbage collected by the .NET Framework, but until it actually performs the collection job, the system can't know (ie. "you don't know what you don't know").
One other point: The value that Task Manager displays includes memory used by the .NET Faremwork to run your app, so it appears bigger than it really is. Some of that code might actually be shared with other .NET apps, but it doesn't appear to be reported that way. Maybe Windows 2003 & future .NET-at-the-core versions of Windows will exclude shared code properly, but for now, we're stuck with it. The good news is that Java's Virtual Machine suffers from the same problem ;-) .
Meanwhile, if you're concerned about how much is being reported, use the timer trick to call GC.Collect every now and then.
Bookmarks