Hi all,
I have this really simple application that has a timer. Every time the timer "ticks" (the interval is set to 1 ms) a screenshot is taken (a new Bitmap object is created and a 100x100 pixel portion of the screen is copied into the Bitmap object). Also a label´s text is updated to display time since starting the timer.
The weird thing is that I get a "Out of memory" exception after the timer has been running a couple of minutes. I tried changing the interval, but I get the same error whether the timer is 1, 10 or 100 ms.
Can someone please explain why this happens? Clearly I have some kind of memory leak in my app, but the only disposable resource I have is the Graphics object, and I think I do dispose it properly. I would like to understand the reason behind this behavior.
Here is (most interesting parts of) the code. The whole solution is attached as .zip.
---Code snippet start---
public partial class Form1 : Form
{
DateTime startTime;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
startTime = DateTime.Now;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
Bitmap bitmap = GetScreenShot();
TimeSpan duration = DateTime.Now - startTime ;
label1.Text = duration.ToString();
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Stop();
}
public Bitmap GetScreenShot()
{
Bitmap screenShotBMP = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
Graphics screenShotGraphics = Graphics.FromImage(screenShotBMP);
try
{
screenShotGraphics.CopyFromScreen(100, 100, 0, 0, new Size(100, 100), CopyPixelOperation.SourceCopy);
}
catch (Exception ex)
{
MessageBox.Show("Error: "+ex.Message );
}
finally
{
screenShotGraphics.Dispose();
}
return screenShotBMP;
}
}
---Code snippet end---
Here is the error that I get:
----Error start----
Error: Out of memory. at System.Drawing.Graphics.FromHdcInternal(IntPtr hdc) at System.Drawing.BufferedGraphicsContext.CreateBuffer(IntPtr src, Int32 offsetX, Int32 offsetY, Int32 width, Int32 height) at System.Drawing.BufferedGraphicsContext.AllocBuffer(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle) at System.Drawing.BufferedGraphicsContext.Allocate(IntPtr targetDC, Rectangle targetRectangle) at System.Windows.Forms.Control.WmPaint(Message& m) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.Label.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.Run(Form mainForm) at TestUIDeleteThis.Program.Main() in C:\Users\Visual Studio 2008\Projects\TestUIDeleteThis\TestUIDeleteThis\Program.cs:line 20
----Error end----
-Jari
Loading
Jari NevalaPosted Sep 3, 2010, 4:16 AM
So yes, this clearly is an issue on my PC:s. I have Windows XP on my both PC:s, and they are both company PC:s and therefore I have not installed all the software by myself.
So guys, do you have any good ideas where to start looking for the reason why CopyFormScreen() leaks GDI object on my machines? I honestly have no idea what can cause behavior like this.
Mahesh ChandPosted Sep 1, 2010, 9:56 PM
Why don't you try the same code on other machine? Ask your friend, your employer or your school to run the code. It seems like problem is your machine, not the code.
Sam HobbsPosted Sep 1, 2010, 7:06 PM
theLizardPosted Sep 1, 2010, 6:09 PM
You have said that you are getting the same problem on your dell and lenov! and still no one else is having the problem!! This is really weird since those who have tried your code are using machines with configurations that are NOT the same as yours and still they are NOT getting the problem.
So what is the common thread between your dell and lenov? what is installed on your machines that may not be installed on ours?
In order for anyone here to help solve your problem they need to have your machines exact configuration using the exact same hardware and installed software otherwise you are the only one that could trace the issue with memory leak analyzers to find where the actual problem is.
Jari NevalaPosted Sep 1, 2010, 10:18 AM
screenShotGraphics.CopyFromScreen(100, 100, 0, 0, new Size(100, 100), CopyPixelOperation.SourceCopy);
So for some reason the CopyFromScreen leaks GDI objects, and from the links I posted above (my 6th post to this thread) you can see this has been a known issue for Miscrosoft.
The weird thing is why you guys do not experience this and I do...=O
Suthish NairPosted Sep 1, 2010, 8:28 AM
screenShotGraphics = Graphics.FromImage(screenShotBMP);
use a simple dummy image instead of object screenShotBMP and run the code.
Check the same problem occurs..
Jari NevalaPosted Sep 1, 2010, 7:28 AM
As I stated before, I have been able to reproduce this on my both PC:s. Other one is a powerful Dell Precision T5400 (2.67 Ghz Xeon, 2.5 GB RAM) and the other one is my laptop, Lenovo Thinkpad R400 (2.19 Ghz Celeron, 2 GB RAM). So no, this is hardly because of poor hardware.
Pasan RatnayakePosted Sep 1, 2010, 6:56 AM
Just to ensure this hypothesis, why don't you run this code in a different machine with the same configuration and also test in on a high-end PC. Furthermore, you can reduce the frequency of the timer and check whether it has a say in it.
PS: http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=621 << This is a good link about the .NET GC
Edit: I just saw the question by Jari. I added the using blocks, so that every time you go out of the using blocks, the dispose methods of those objects would be automatically called. It helps the GC to directly sweep the objects off the memory. I did not use GC.Collect() directly just so that calling the GC manually is also a resource centric process. Since you were already having problems with memory I thought it won't help. But surely it seems to be help resolve your issue.
Suthish NairPosted Sep 1, 2010, 6:39 AM
Jari NevalaPosted Sep 1, 2010, 5:36 AM
I added a checkbox to enable disable the forced garbage collection. So as soon as I check the "Force Grabage Collection" checkbox after starting the timer, the GDI Objects stop increasing.
The question now is why I need to force the GC in order to prevent the GDI object leak? Adding the using statements for Bitmap objects does not change the behavior. Also, changing the timer1 interval to 1 second (instead of 1 millisecond) does not fix the problem for me. It just takes longer for the GDI objects to reach 10000.
----
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
namespace TestUIDeleteThis
{
public partial class Form1 : Form
{
DateTime startTime;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
startTime = DateTime.Now;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
Bitmap bitmap = GetScreenShot();
//do something with the bitmap..
TimeSpan duration = DateTime.Now - startTime;
label1.Text = duration.ToString();
if (chkForceGC.Checked)
{
GC.Collect();
}
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Stop();
}
private Bitmap GetScreenShot()
{
Bitmap screenShotBMP = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
Graphics screenShotGraphics = Graphics.FromImage(screenShotBMP);
try
{
screenShotGraphics.CopyFromScreen(100, 100, 0, 0, new Size(100, 100), CopyPixelOperation.SourceCopy);
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
finally
{
screenShotGraphics.Dispose();
}
return screenShotBMP;
}
}
}
----
Sam HobbsPosted Sep 1, 2010, 5:03 AM
I am not Jari, but I hope Jari does not mind me saying that the purpose of the program is to test and debug a piece of code. It is a sample for development purposes and is not intended to do anything at all except execute without errors.
Jari NevalaPosted Sep 1, 2010, 5:01 AM
I just noticed that forcing the garbage collection fixes the problem. I added GC.Collect() to the end of the timer1_tick and not the GDI objects stay at approximately 40. Im gonna investigate this a bit more, I´ll post some code soon. I want to understand the behavior behind this. Also, I remember reading somewhere that one should almost never force the garbage collection, since the .Net Framework knows better when to collect 99% of times...(but maybe my case is withing that 1%? =)
By the way, isnt the Graphics -object the only object in my code that needs disposing, since its the only one that implements IDisposable? Bitmap does not implement it, but then again its inherited from Image that does implement IDisposable....just wondering why you added the using statements Pasan?
Pasan RatnayakePosted Sep 1, 2010, 4:53 AM
It may seem like he is not actually allocating memory. But if you have used the Windows APIs for this task you know that the underlying API that helps to capture from screen is using byte buffers. The .NET classes hides these complexities but it does not mean that the need to release memory is not required.
But of course, this is only my opinion which may be wrong. What I would like to see is how you are capturing the statistics of the program and what is the purpose of this program since I don't see any use of the Bitmaps captured.
Sam HobbsPosted Sep 1, 2010, 4:00 AM
Jari NevalaPosted Sep 1, 2010, 3:27 AM
This is getting really weird. But since you guys didnt get the same error, it seems that the problem is in my system, not in my code. Any ideas? All I can think of is re-installing .Net framework...
What OS are you guys running? Im running Win XP SP2 (actually just noticed that I probably should install SP3...its a company PC so I thought SP3 would be in place already..)
Edit:
Pasan: No, I dont think I have any compiler modifications, at least I dont know about any.
I also tried tho run the application on my friend´s PC that has Win XP SP3 installed, the GDI Objects kept increasing on his machine too..
Pasan RatnayakePosted Sep 1, 2010, 3:23 AM
Not really. But I did notice two things.
1. The timer interval is set to 1 millisecond which in turns runs the GetScreenShot() every millisecond. This would be strenuous on a PC with a low RAM or processing power.
2. The next thing was that none of the memory allocated was disposed explicitly. Therefore, the garbage collector doing it on behalf of him. So a low performance PC running along with other resource demanding application(s) could be the underlying cause.
Then again these are only valid, given that he has not done any kind of compiler modifications. All I did in my modifications was to handle the memory disposing efficiently which cut down the usual memory allocation for this application by half.
Sam HobbsPosted Sep 1, 2010, 1:42 AM
Pasan RatnayakePosted Sep 1, 2010, 1:06 AM
I just updated your code and uploaded it with this reply. I tested it for quite some time and it never ran out of memory.
Just check out this code and let me know if this solves the issue.
Regards
Sam HobbsPosted Aug 31, 2010, 7:58 PM
theLizardPosted Aug 31, 2010, 7:08 PM
Some stats:
Mem Usage: 9,900K
Peak Mem Usage: 9,940k
Mem Delta: 0 - 54K
User Objects: 21
GDI Objects: 36
it is now 7 minutes after start of test.
Hope this helps, but I think your problems are elsewhere.
Jari NevalaPosted Aug 31, 2010, 5:30 AM
This is really annoying..
Sam HobbsPosted Aug 31, 2010, 5:26 AM
Jari NevalaPosted Aug 31, 2010, 5:11 AM
I noticed that I actually do have .Net version 3.5 installed on my system. The reason why I thought that I only have 2.0 is because System.Environment.Version.ToString() only returns the 2.0 version string, even if you have newer versions installed (I found this info from this site).
Accessing this site (using Internet Explorer!) tells me that that I have 3.5 SP1 installed. Also the windows registry and add/remove programs verifies this.
So the question is still open: why does my application leak GDI objects? How could I prevent it from happening?
When the number of GDI objects reach 10000 (this can be monitored in the task manager by adding the GDI object column), I get the "Out of memory" exception.
I have attached the solution in a zip file. Can someone please check if the same happens on your system?
Sam HobbsPosted Jul 21, 2010, 2:51 AM
theLizardPosted Jul 21, 2010, 12:26 AM
If you look at the reg value it will be set at 271 which equals 10000 handles try setting this value to 1000, this should give you 36,900 handles, if you can then run your application for longer (almost 4 times longer) you will have found the problem.
If you still get the error message ofter a couple of minutes then something is drastically wrong...
Jari NevalaPosted Jul 21, 2010, 12:08 AM
I also tried moving the Graphics object to be a class member but that didnt help. Number of GDI objects keeps still rising when the timer is enabled.
theLizardPosted Jul 20, 2010, 6:04 PM
There seems to be a problem with System.Drawing.Graphics.FromHdcInternal(IntPtr hdc) and the reason why you still have ample memory available is that it is not the memory that is being consumed, it is (from what I understand) that you have exhausted the number of allowable handles of the process and the problem does not appear to be net version or platform related it is a known BUG, I only found this out because you were still getting the error in my example which you should not have if dispose did its job.
If you run out of handles, you can no longer create the GDI objects. Have a look at how many handles per process you have in your registry under
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota
This value can be set to a number between 256 and 65,536 this is where you problem could be. Failing this well it's any ones guess..
If it does fix the problem then consider not creating a new graphic object each time you want to create the bitmap, re use the one you have by over writing it with the new image (if that can be done).
Jari NevalaPosted Jul 20, 2010, 4:26 PM
Sam HobbsPosted Jul 20, 2010, 4:20 PM
Whatever the version, it is later than 2.0 and when I run your sample code the number of GDI Objects never gets much more than 200; sometimes it is 204 or 212 but then it drops down. It has been running for more than 10 minutes now with the number of GDI Objects staying in the 50-200 range, or close to that. So apparently garbage collection is deleting the GDI Objects adequately.
Jari NevalaPosted Jul 20, 2010, 4:20 PM
Anyway, as you can see from my code in the first post, I am calling Dispose in the finally statement. I really have no clue what/how else I would dispose the Graphics object.
I have been able to reproduce this on my both PC:s. Other one is a powerful Dell Precision T5400 and the other one is my laptop, Lenovo Thinkpad R400. So no, this is hardly because of poor hardware.
Could you guys try and see if you can reproduce this by enabling the GDI objects in the Task manager (View -> Select columns), leave the application running and see if the number of GDI objects increases and if you get the OutOfMemory error when it reaches 10000?
And Sam: what .Net framework you have installed?
Mahesh ChandPosted Jul 20, 2010, 3:45 PM
Sam HobbsPosted Jul 20, 2010, 3:41 PM
Jari NevalaPosted Jul 20, 2010, 2:27 PM
Sam: On what .Net version you tried my code? If it is later than 2.0, then it probably means that the GDI object leak bug is fixed in the later version.
Mashes: I think I will try to implement this using WPF as soon as I get a newer .Net framework installed, thanks for the tip.
Lizard: I tried your code (even tough I already tried having the Bitmap as a member object as I stated in my previous posts). I got the same error with your code too, meaning I got the same "GDI objects reach 10000" thing.
Here is a screenshot. As you can see, the process memory consumption is just 26 MB, but the number of GDI objects is 10000 (actually a bit less in the screenshot because a few of them are released after the exception) and thats when the OutOfMemory exception occurs.
Like Sam said, the example code is just an example, but what I am trying to achieve here is a monitoring application that takes a screenshot every 0.1 sec and analyzes if something has changed in it. That way I can plug an external device to my video capture card´s HDMI input and measure some delays..
Sam HobbsPosted Jul 17, 2010, 3:47 PM
If a class creates unmanaged resources, it should also delete those resources using the corresponding unmanaged functions. If it does not then the documentation of that object needs to state explicitl what needs to be done.
Note that the sample code in the documentation of the Bitmap class does not call Dispose or Finalize or anything such as that.
Mahesh ChandPosted Jul 17, 2010, 8:49 AM
WPF on the other hand is totally rewritten from scratch.
Sam HobbsPosted Jul 17, 2010, 2:25 AM
The sample code is just a sample. The source code says something about screenshots of Media Express, so I assume that the bitmaps will be written to disk in the final version. I don't know if it would be possible to inject a thread into Media Express process, but (1) that would have to be done using unmanaged code and (2) anti-virus software would not like it.
theLizardPosted Jul 16, 2010, 10:19 PM
The system will use virtual memory in the form of disk space but this too will eventually be exhausted ending up in a major system crash.
Yes I to do not know where the bitmap is actually used, if there is a reason to do this for the same specific screen location other than experimental reasons then at the very least the bit map should be saved to disk.
But we do not know what the purpose of this exercise is!!!
Sam HobbsPosted Jul 16, 2010, 9:21 PM
Why does the garbage collector not dispose of the bitmaps? I don't see any place that a bitmap would be used after the timer tick.
Sam HobbsPosted Jul 16, 2010, 9:14 PM
theLizardPosted Jul 16, 2010, 8:40 PM
you are creating a new bitmap each time you call GetScreenShot()
try this...
public partial class Form1 : Form
{
DateTime startTime;
Bitmap screenShotBMP = null;
}
private void timer1_Tick(object sender, EventArgs e)
{
screenShotBMP = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
GetScreenShot(); //get image
TimeSpan duration = DateTime.Now - startTime;
label1.Text = duration.ToString();
if (screenShotBMP != null) //release the GDI resource
screenShotBMP.Dispose();
}
private void button1_Click(object sender, EventArgs e)
{
startTime = DateTime.Now;
timer1.Start();
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Stop();
}
public void GetScreenShot()
{
Graphics screenShotGraphics = Graphics.FromImage(screenShotBMP);
try
{
screenShotGraphics.CopyFromScreen(100, 100, 0, 0, new Size(100, 100), CopyPixelOperation.SourceCopy);
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
finally
{
screenShotGraphics.Dispose();
}
}
Sam HobbsPosted Jul 16, 2010, 6:14 PM
I will try building your project as it exists and run that.
Mahesh ChandPosted Jul 16, 2010, 8:31 AM
I suggest you upgrade to .NET 4.0. Just download Visual Studio 2010 Express (search Google) from MSDN and build WPF applications. Check out WPF section of this site for articles and tutorials on it.
Good luck!
Jari NevalaPosted Jul 16, 2010, 4:20 AM
http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/7c4d2e73-6e73-4f10-a614-13fd76b2f419/
and
http://social.msdn.microsoft.com/forums/en-US/winforms/thread/8a0599bd-c309-40d1-8ed3-ed7a48c1853c/
I found out that my application throws the OutOfMemory exception exactly when the number of GDI objects for the process reaches 10000 (this can be seen in the Task Manager when GDI Objects column is visible). I am disposing my Bitmap and my Graphics objects, so I guess there is nothing to do to prevent the GDI objects from reaching 10000.
So it seems that the problem is not in my application, its in fact in the .Net framework.
So what I should do now is update the .Net framework to 3.5 and see if the problem still exists. Or do you guys think that it would make more sense to ditch GDI+ approach and try to implement this using WPF?
Jari NevalaPosted Jul 16, 2010, 3:51 AM
I would be really pleased if someone more experienced would take a look at the code I posted above. It probably wouldnt take long since the code of the whole app is like 30 lines of code. I just want to see how this would be done properly.
Sam HobbsPosted Jul 14, 2010, 12:14 PM
A device context is something that Windows uses to draw into a window. You need to get a book about Windows programming that explains Windows graphics programming; there is too much to explain here and there are ample books that explains it.
Jari NevalaPosted Jul 14, 2010, 5:04 AM
private void timer1_Tick(object sender, EventArgs e)
{
Bitmap bitmap = GetScreenShot();
//here I would analyze the bitmap using bitmap.GetPixel()
bitmap.Dispose();
}
So I added a Dispose-call for the bitmap like Mahesh suggested, and removed the label1.text setting (to avoid accessing the form from the separate thread)
But like I said, something still eats up the memory...
Jari NevalaPosted Jul 14, 2010, 4:44 AM
And what "device context" actually means?
I have no experience in WPF, and currently I have only .Net framework 2.0 installed. But maybe this would be a good change to learn some WPF stuff. Any tutorials you guys would suggest? Anyway, I would like to understand the reason for the OutOfMemory exception fully in this GDI+ version before I start learning how to do this on WPF..
I would be really happy if you guys would modify the code I posted and tell me how you would do this (and avoid the OutOfMemory exception)
What Im actually trying to develop here is a monitoring app where I take a screenshot like every 10 millisecond and then analyze it to see if there has been changes in the captured bitmap.
Sam HobbsPosted Jul 13, 2010, 8:34 PM
The exception appears to be occuring in FromHdcInternal. So there is probably a problem getting a device context. That might be consistent with the problem that Mahesh describes or consistent with what I say above or maybe consistent with both. Since Mahesh's suggestion is something you are supposed to do anyway, then do that. Also ensure that you are noit using the form from another thread, as I describe above.
Mahesh ChandPosted Jul 13, 2010, 11:17 AM
Do you have an option to use WPF? It yes, I suggest you use WPF for all graphics work, not GDI+.
Jari NevalaPosted Jul 13, 2010, 11:06 AM
Anyway, my question was more theoretical - that's why I don't actually do anything with the Bitmap object in this example app. Why does the memory run out in my original case? I mean, if I create a new Bitmap object with the same name every time my timer ticks, isn't the old object replaced with the new one (meaning no new memory is reserved for the new Bitmap object?)...and since my Bitmap is created inside the tick handler, doesnt the garbage collector free the memory when the execution leaves the timer1_Tick?
Mahesh ChandPosted Jul 13, 2010, 10:45 AM
Bitmap bitmap = GetScreenShot();
Bitmap screenShotBMP = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
Graphics screenShotGraphics = Graphics.FromImage(screenShotBMP);