Reputation: 21
I have a two Windows Form Program that is a picture database and slide show. The
slide show continuously updates the displayed picturebox without incrementing the
memory used with each new switch of picture. However, I have a second form that
has 50 pictureboxes that are meant to display thumbnails of pictures to be added to the database. As each picturebox of size (57,40) is updated with a small-sized thumbnail(<12K), the memory used by the IDE rises to a little over 1GB on my 32bit XP system, from about 660MB before any thumbnail pictureboxes are loaded.
With around 30 of the size (57,40) pictureboxes are loaded from .jpg source files >3MB, the IDE memory use rises to around 2.1GB. (Problem is not easily encountered with .jpg images <=15K and all 50 thumbnails can be used with <1.3 GB mem used).
Problem shows itself with images of average .jpg file size >3MB per HD Canon camera .jpg image are loaded into 30 of the displayed thumbnail pictureboxes and I begin to start to click around selecting pictures displayed and memory used rises quickly and over the 2.3GB memory usage causing Out of Memory crash.
Is this a bug in VB2010 or .NET 4.0 ?
Once all the pictures are displayed as thumbnails, a mouse click on any of the 50 pictureboxes on the panel containing all thumbnails updates a single large picturebox on the form itself to display the picture in a single picture box with a size(1024,768). When any thumbnail is clicked, the single large Picture box displays it's associated picture from a file, but at the same time, system memory increments about 240KB per click event. Eventually, at around >2.3GB of system memory used, the program crashes with an Out of Memory error.
How can I get the program to recover the memory used by the same picturebox when
it is updated with just another picture?
Partial Code below:
' Each Thumbnail has a click event
'PB49 is a PictureBox max size (57,40) used as a thumbnail display, all 50 are on a panel
Private Sub PB49_Click(sender As System.Object, e As System.EventArgs) Handles PB49.Click
'PB(50) is an Integer Array flagging Pictures to add
If PB(49) = 1 Then PB(49) = 0 Else PB(49) = 1
If PB(49) = 1 Then
CheckBox49.Checked = True 'Tiny Checkbox on thumbnail
F$ = ListAddFiles.Items(48) 'ListFileBox of FileNames
PBx1.Image = Image.FromFile(F$) 'Gets filename and path and loads image into PictureBox
PBx1.Visible = True 'Large PictureBox (1024,768)shows Pic F$ located on Form
Else
CheckBox49.Checked = False
PBx1.Image = Nothing
PBx1.Visible = False
End If
End Sub
I am using Visual Studio Ultimate SP1 updated .NET 4.0
on XP PRO 32-Bit SP3 4GB RAM
installed.
Upvotes: 2
Views: 4897
Reputation: 28345
@AaronLS gave an ultimate teoretic answer, so read and remember it.
The memory leak in your code is here:
PBx1.Image = Nothing
PBx1.Visible = False
PBx1.Image is a Image
type object, as you use Image.FromFile
method. And Image
is a IDisposable
, which means that it uses native resources in unmanaged code.
You must explicitly call the .Dispose() method for PBx1 in this code:
'PBx1.Image = Nothing
If PBx1.Image IsNot Nothing Then PBx1.Image.Dispose() End If
PBx1.Visible = False
Upvotes: 2
Reputation: 38367
When you are hiding the images, is it possible that an image gets loaded again. I.e. you hide it, and the hidden one stays there, and it gets added again later. Hence you are accumulating a huge number of hidden images?
"Memory leaks" in .NET applications are often the result of not nulling out references or removing an object from all collections. As long as an object has a reference from somewhere, either some control in a control collection in a form, or one of your own collections, then it will remain in memory. I'm not sure what you logic is for showing/hiding picture, but maybe you need to be removing the picture, and making sure you clear references to it from all relevant controls. With the way control collections can be nested, it would be easy to miss a place. There is probably alot of additional overhead with image related controls, so if you have lots of unused controls hidden but sitting in memory, then that can be a problem.
You say you have 50 picture boxes, but do you have more pictures than that in the collection not displayed. If you have loaded them into a collection, then regardless of whether they are displayed or not, they may be taking up memory.
Couple things to be aware of.
1) A process in 32bit Windows has max of 2GB memory available to it. There are ways to configure this to 3gb on a machine, but generally you shouldn't expect to have more than 2gb available. Ojn On top of that, the usual memory fragmentation can cause you to get OutOfMemory exceptions even when it seems you have lots of free memory.
2) Whenever you are dealing with .NET collections, usually internally they are implemented as arrays. An array is a contiguous block of memory. Therefore even when you have plenty of free memory, it is possible to get an OutOfMemory exception because there isn't a alrge enough contiguous block of memory(due to fragmentation). .NET tries to reduce fragementation, but sometimes it is like moving a giant desk around in a crowded room, it can't work miracles.
3) since arrays do not expand dynamically, internally the collection must alocate a new array whenever it runs out of space. Uusually it doubles the array size each time. This is the ".Capacity" property of most collections. You should use some debugging to monitor the .Capacity and .Length of the collection. You will observe it double everytime the collection reaches that size. I.e. 128, 256, etc.
Whenever it doubles, it must allocate a new array, and then copy the contents of the old array before deallocating the old array. Hence, if you had a collection with 256(or some other power of 2 like 64) items, and then added one more item, internally the collection allocates a new 512 item array, and then copies from the previous array into it. During this transitional process you have both a 512 and 256 array in memory. So while you only need space for 257 items, to add the 257th item requires three times as much space (256+512=768).
You can use the stack trace to determine if the exception is occurring while trying to add a picture to the collection, since it is in the call that adds the time is where the collection expands if needed. You will also notice the Length just before the call will be a power of 2.
So if you have 512mb of images and the collection needs to expand to add one more, .NET has to find space to allocate a 1gb array of images. So during that transition of the collection having to expand its capacity, it will require 1.5 gb of ram. It will be VERY DIFFICULT for .NET to find a contiguous block of 1gb of ram due to fragmentation, even if with all the steps .NET takes to minimize fragmentation, there is still alot of its control. (Someone will probably point out that an array of references is much smaller, but it's hard to say what you are dealing with.)
Solution: If you determine this is the cause of your problem, then the solution is to predict how many items you will need, and set the capacity of the array in advance. So if you have a list of images, then it'd be better to get a count of those images first, and then set the Capacity of the collection to that amount, and maybe plus 10% more or something. This way if you have 300 items, you can set the capacity to 350. That way you have instead estimated the capacity in advance with a little head room, then it will never have to expand, and thus won't experience the 3x spike in memory usage for the expansion.
Google .NET memory profilers. There's lots of profilers and also win debug that will let you see details of memory allocation and deallocation as well as fragmentation.
Upvotes: 2