内存泄漏检查课件_第1页
内存泄漏检查课件_第2页
内存泄漏检查课件_第3页
内存泄漏检查课件_第4页
内存泄漏检查课件_第5页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、www.AutomationQA.com内存泄漏检测方法 对于不同的程序可以使用不同的方法来进行内存泄漏的检查,还可以使用一些专门的工具来进行内存问题的检查,例如MemProof、AQTime、Purify、BundsChecker等。 也可以使用简单的办法:利用Windows自带的Perfmon来监控程序进程的handle count、Virtual Bytes和Working Set 3个计数器。 Handle Count记录了进程当前打开的句柄个数,监视这个计数器有助于发现程序是否存在句柄类型的内存泄漏;Virtual Bytes记录了程序进程在虚拟地址空间上使用的虚拟内存的大小,Vir

2、tual Bytes一般总大于程序的Working Set,监视Virtual Bytes可以帮助发现一些系统底层的问题;Working Set记录了操作系统为程序进程分配的内存总量,如果这个值不断地持续增加,而Virtual Bytes却跳跃式地增加,则很可能存在内存泄漏问题。 堆栈内存泄漏 堆栈空间不足会导致在受托管的情况下引发StackOverflowException类型的异常,线程泄漏是堆栈内存泄漏的其中一种。线程发生泄漏,从而使线程的整个堆栈发生泄漏。 如果应用程序为了执行后台工作而创建了大量的工作线程,但却没有正常终止这些线程,则可能会引起线程泄漏。 一个堆栈内存泄漏的例子:pr

3、ivate void button1_Click(object sender, EventArgs e) / 循环启动多个线程 for (int i = 0; i < 1500; i+) Thread t = new Thread(new ThreadStart(ThreadProc); t.Start(); static void ThreadProc() Console.WriteLine("启动 Thread #0 ",Thread.CurrentThread.ManagedThreadId); / 阻塞直到当前线程结束 Thread.CurrentThread

4、.Join(); 利用Perfmon检测线程堆栈泄漏 默认堆栈大小为1MB,因此如果应用程序的Private Bytes不断增大,同时.NET CLR LocksAndThreads中的 # of current logical Threads 也相应地增大,那么就很可能是发生了线程堆栈泄漏。 可以利用Perfmon来判断是否存在内存泄漏现象。 执行被测试程序的相关操作,并在性能监视器中密切注意“Private Bytes”和“# of current logical Threads”两个计数器的变化曲线,如果Private Bytes不断增大,同时# of current logical T

5、hreads 也相应地增大,则可判断程序发生了线程堆栈泄漏。 用CLRProfiler定位线程泄漏代码利用CLRProfiler可以帮助检查程序是否存在线程泄漏。方法如下:(1)启动CLRProfiler (2)单击“Start Application”按钮 (3)选择需要测试的应用程序,单击“打开”按钮。CLRProfiler会自动打开被测试程序,执行程序的相关操作,然后单击CLRProfiler的“Show Heap Now”按钮 说明:这个界面显示了程序的所有堆分配的情况。其中可以看到线程类中分配了82K,占了18%以上,其中包含1500个线程对象。(4)选中“Threading.Thr

6、ead”的节点,单击右键,选择“Show Who Allocated” 说明:在这个界面中可以看到是哪个类的哪个方法创建了这么多的线程对象,在这里可以看到是由button1_Click方法调用了线程类,从而定位到引发线程泄漏的代码。 资源泄漏 资源通常指系统的对象。例如GDI对象句柄、内存句柄等,在软件编程过程中,使用到很多这些资源对象,但是没有及时地释放掉就造成了资源泄漏。 GDI泄漏是指程序申请了GDI句柄,但是没有及时释放,导致GDI句柄不断累积。GDI泄漏可能导致系统不稳定,或者出现花屏。一个GDI泄漏的例子 : Form1: / 调用Form2窗体 Form2 f = new For

7、m2(); / 显示Form2窗体 f.ShowDialog(); Form2: private void Form2_Load(object sender, EventArgs e) / 使用pictureBox控件加载并显示一个图片 pictureBox1.Image = Image.FromFile("picture.JPG"); private void Form2_FormClosing(object sender, FormClosingEventArgs e) / 如果少了这句,则会发生GDI资源泄漏 /pictureBox1.Image.Dispose();

8、 用Windows任务管理器协助检测GDI泄漏 对于上面的GDI泄漏代码,可以利用Windows的任务管理器来协助检测。方法如下:(1)首先打开Windows任务管理器 (2)选择菜单“查看 | 选择列”,出现如图15.13所示界面。确保“GDI对象”被勾选上,然后单击“确定”按钮。(3)启动被测试程序ResourceLeak(即上面的代码例子的可执行程序),并在Windows任务管理器中定位到被测试程序的进程 (4)记下应用程序进程的当前GDI对象数,然后运行程序的各项操作,在操作过程中密切关注其GDI对象数的变化,例如,对于ResourceLeak.exe进程,当前的GDI对象数是33,如

9、果点击button1,程序将调出第二个窗口,窗口加载了一个图片,这个过程会向系统申请一些GDI对象资源,因此查看Windows任务管理器可以看到其GDI对象数的变化 (5)这时候,把第二个窗口关闭,如果程序存在资源泄漏,则GDI对象数不会减少到33。而且反复操作程序,调出第二个窗口再关闭,可看到GDI对象数不断地增加,这样就可判断程序存在GDI资源泄漏的现象。 利用GdiUsage 检查GDI泄漏 GdiUsage是Christophe Nasarre写的一个专门用于检查程序使用GDI资源情况的小工具 它的使用方法也很简单,具体使用方法如下:(1)首先在上面的输入框输入需要测试的程序路径,然后

10、按“Start”按钮启动被测试程序,程序被启动的同时,GdiUsage会显示一个“Debuggee Output”窗口,用于展示程序加载的DLL名称以及地址 (2)启动程序后,在GdiUsage中单击“Take Snapshots”按钮,给当前程序使用的GDI资源情况取一个“快照” (3)可看到当前程序使用到1个Bitmap类型的GDI对象,单击“Details”按钮,还可以看到详细的资源展示界面 (4)接着操作被测试程序(单击ResouceLeark程序的button1按钮),再单击一下“Take Snapshots”按钮,给当前程序使用的GDI资源情况取一个“快照” (5)可以看到当前程序

11、使用的Bitmap对象增加到2个,Region对象增加1个。这时关闭ResouceLeark程序的Form2窗口,再取一个快照,则发现Bitmap对象和Region对象的个数都未减少,并且如果重复这个过程,Bitmap对象的个数会不断地增加。因此可以认为程序存在资源泄漏的现象。GDI与GDI+1、概述 GDI在全称是Graphics Device Interface,即图形设备接口。是图形显示与实际物理设备之间的桥梁。GDI接口是基于函数,虽然使程序员省力不少,但是编程方式依然显得麻烦。例如显示一张位图,我们需要进行“创建位图,读取位图文件信息,启用场景设备,调色板变化“等一系列操作。然而有了

12、GDI+,繁琐的步骤再次被简化。顾名思义,GDI+就是GDI的增强版,它是微软在Windows 2000以后操作系统中提供的新接口。2、GDI+主要功能 GDI+主要提供以下三种功能: (1) 二维矢量图形:GDI+提供了存储图形基元自身信息的类(或结构体)、存储图形基元绘制方式信息的类以及实际进行绘制的类; (2) 图像处理:大多数图片都难以划定为直线和曲线的集合,无法使用二维矢量图形方式进行处理。因此,GDI+为我们提供了Bitmap、Image等类,它们可用于显示、操作和保存BMP、JPG、GIF等图像格式。 (3) 文字显示:GDI+支持使用各种字体、字号和样式来显示文本。 相比于GD

13、I,GDI+是基于C+类的对象化的应用程序接口,因此用起来更为简单。GDI的核心是设备上下文,GDI函数都依赖于设备上下文句柄,其编程方式是基于句柄的;GDI+无需时刻依赖于句柄或设备上下文,用户只需创建一个Graphics 对象,就可以用面向对象的方式调用其成员函数进行图形操作,编程方式是基于对象的。3、GDI绘制实例 GDI在使用设备上下文绘制线条之前,必须先调用SelectObject 以使笔对象和设备上下文关联。其后,在设备上下文中绘制的所有线条均使用该笔,直到选择另一支不同的笔为止。 使用GDI画线代码如下/ TODO: Add your command handler code h

14、ere CClientDC clientDC; /目标DC CPen pen (PS_SOLID, 1, RGB(0, 0, 255); clientDC.SelectObject(pen.GetSafeHandle(); /开始绘制 clientDC.MoveTo(0, 0) clientDC.LineTo(rect.right, 0); clientDC.SelectObject(oldObject); 从上述代码可以看出:在GDI编程中,几乎所有的操作都围绕设备上下文dc展开。的确,这正是GDI编程的特点!设备上下文是 Windows 使用的一种结构,所有GDI操作前都需取得特定设备的上

15、下文,函数中的CClientDC dc (this) 语句完成这一功能。 利用GDI进行图形、图像处理的一般操作步骤为:1. 取得指定窗口的DC。2. 确定使用的坐标系及映射方式。3. 进行图形、图像或文字处理。4. 释放所使用的DC。但是,在GDI+中,只需将Pen对象直接作为参数传递给Graphics类的DrawLine等方法即可,而不必使Pen对象与 Graphics对象关联。4、GDI+绘制实例 使用GDI+画线代码如下 / TODO: Add your command handler code here CClientDC clientDC (this); /创建Graphics对象

16、Graphics graphics(clientDC);/创建penPen myPen;myPen.SetWidth(1);/画X轴myPen.SetColor(Color:Blue);graphics.DrawLine(&myPen, 0, 0, rect.right, 0); (1)创建 Graphics 对象:Graphics 对象表示GDI+绘图表面,是用于创建图形图像的对象。 (2)使用 Graphics 对象绘制线条和形状、呈现文本或显示与操作图像。 GDI+的相对与GDI而言,新增了一系列功能:渐变的画刷(Gradient Brushes)、基数样条函数(Cardinal

17、 Splines)、持久的路径对象(Persistent Path Objects)、变形和矩阵对象(Transformations &Matrix Object)、可伸缩区域(Scalable Regions)、Alpha混合(Alpha Blending)和丰富的图像格式支持等。下面,我们来逐个用实际代码实现GDI+的新增功能。非托管资源造成的内存泄漏在.NET开发中容易被忽视,引起内存泄漏通常存在于以下三种情况:1. 对象被引用而没有被释放2. 没有释放非托管资源3. 没有释放非托管资源封装对象上个章节描述的EventHanlder和Delegate造成的内存泄漏就属于第一类,之

18、所以要单独拿出来叙述是因为.NET事件和代理造成的内存泄漏相对于静态变量的根化引用更容易被忽略且不好被识别出来。第2类和第3类同属于系统资源类型造成的内存泄漏,但是又有所区别。第二类是指通过本地API函数与托管对象进行交互(比如:通过 P/Invoke方式调用本地DLL,DLLImport声明静态外部函数和COM Interop)所用到的非托管资源。例如:当通过DLL Import调用 API函数GetDC函数时忘了调用ReleaseDC去释放设备句柄造成4个字节的内存泄漏。再如:智能文档中使用的Word以及导出EXCEl功能用到的Office的COM非托管组件,在关闭时GC不能识别COM组件

19、而造成有时候无法对COM对象进行释放,这时候可以通过以下两个InteropServices函数进行释放l System.Runtime.InteropServices.Marshal.ReleaseComObject(comObject);l System.Runtime.InteropServices.Marshal.FinalReleaseComObject(comObject);上次在敏捷交流了内存相关事项问题后,给大家留了几道思考题,其中第一道题是“数据库连接SqlConnection是不是非托管资源,为什么?”,有些人的回答是“肯定”,之所以有这样回答是因为大家所了解的非托管资源的经

20、典认知就是数据库连接、文件、网络连接都是非托管资源,有人认为SqlConnection就是数据库连接,其实不然,.NET对某些非托管资源提供一种包装类,SqlConnection就是这种,包装类的源(WrapSource)才真正是托管资源,它管理了非托管资源,而它本身确实托管的。.NET GDI Plus中常用的Drawing命名空间下的类很多就是这种包装类型,现将常用的几种非托管包装类列举如下:ApplicationContextComponentComponentDesignerBrushContainerContextCursorFileStreamDataSetFontIconImag

21、eMatrixTextureOdbcDataReaderOleDBDataReaderPenRegexSocketStreamWriterTimerTooltipBitmap识别这种包装类型的主要方法就是通过MSDN查询是否该对象继承于System.MarshalByRefObject类。做一个实验来测试Graphics的释放,新建一个Form对象,在Form对象的Paint事件里,写入以下代码,用于在Form2上绘制。Bitmapbmp = newBitmap(600, 600);Graphicsg = Graphics.FromImage(bmp);Brushbrush = newLine

22、arGradientBrush (newPointF(0.0f, 0.0f),newPointF(700.0f, 300.0f),Color.Blue, Color.Red);for (intj = 0; j< 60; +j)for (inti = 0; i< 60; +i)g.FillEllipse(brush, i * 10, j * 10, 10, 10);this.CreateGraphics().DrawImage(bmp, 0, 0);运行起来发现,不停的移动Form2,对应刷新Form2的Paint事件,在内存管理器里面可以看到实验程序的内存会不停的增长,这说明这时候

23、已经产生了内存泄漏了。启动AQTime,并启用Resource Profiler调试方案,运行程序,隔一段时间调用“Get Result”收集数据。第一次收集数据,GpGrahics对象的LiveCount =3;第二次收集数据,GpGrahics对象的LiveCount =21;GpGraphics对象的持续增长说明,GpGraphics造成了内存泄漏,再利用.NET Memory Profiler捕捉内存Heap快照。显示方法中的Bitmap、Graphics、LinearGradientBrush三种类型出现了“Undisposed Instances”警告。这里,因为Graphics没

24、有释放导致Grahics上引用的Bitmap,以及Bimap上的LinearGradientBrush对象都没有被及时释放,造成内存泄漏。将代码修改如下:using (Graphicsg1 = this.CreateGraphics() using (Bitmapbmp = newBitmap(600, 600) using (Graphicsg = Graphics.FromImage(bmp) using (Brushbrush = newLinearGradientBrush (newPointF(0.0f, 0.0f),newPointF(700.0f, 300.0f),Color.B

25、lue, Color.Red) for (intj = 0; j< 60; +j)for (inti = 0; i< 60; +i)g.FillEllipse(brush, i * 10, j * 10, 10, 10); g1.DrawImage(bmp, 0, 0); 再运行AQTime和.NET Memory Profiler,可以看到.NET Memory Profiler的警告消除了,AQTime显示GpsGraphics的Live Count一直是1,不再会增加。由此得知,.NET中的Drawing托管对象使用也会造成内存泄漏,以上泄漏的问题很容易被忽视,因为如果这种泄

26、漏内存的增长量不大,在整个程序运行时显得微不足道,而不容易察觉,另外因为Form作为继承了Idisposable接口的控件容器,在其关闭时会自动调用其每个被引用对象的Dispose方法,所以最后Form也会帮你回收的。Form的Dispose方法如下:protectedoverridevoidDispose(booldisposing) if (disposing&& (components != null) /释放每个被引用对象的Dispose方法components.Dispose(); base.Dispose(disposing); 通常,好的编程习惯要求程序员在使用完

27、非托管资源的对象后应尽快释放不再使用的对象和资源来避免潜在的内存泄漏。释放这种包装对象的方法有3种:l 显式通过Dispose方法释放(推荐)例如:font.Dispose();l 隐式通过Using语句释放(推荐)Using (Font font = new Font(Label1.Font.Name, currentSize, Label1.Font.Style)l 通过Finalization方法(不推荐)不推荐,因为用 Finalize 方法回收对象使用的内存需要至少两次垃圾回收,当垃圾回收器回收时,它只回收没有终结器(Finalize方法)的不可访问的内存,这时他

28、不能回收具有终结器(Finalize方法)的不可以访问的内存。注:什么是非托管资源非托管资源是指CLR不能控制或管理的部分,这些资源一般不存在与Heap中,与非托管资源不同,托管资源一般指被CLR控制的内存资源,这些资源可以由CLR来控制,比如程序中的对象以及变量等,在.NET中,CLR提供一种独立的内存管理机制,不需要程序员在代码中显式的去释放使用到的内出资源,这种管理机制称为GC(Garbage Collection),而对于非托管资源,CLR只能跟踪非托管资源的生存期,而不能主动的去做GC,这样就需要去显式或隐式的去释放这些资源,.NET提供的Fianlize和Dispose方法分别就是

29、隐式和显式释放只要的方法。AQTime测试代码资源分配(Resource Profiler)1).新建项目File->New Project.2).在Setup添加LuboView.exe。3).选择Profiler为Resource Profiler。4).点击Run按钮,在弹出的对话框中点击Run。5).在Event View中可以查看到以下信息:Event   Thread IDTime-Project run selected, current profiler is Resource Profiler.   15:18

30、:30:843  Product: AQtime; Version: 4.92.669.0    -Work environment:      Host name: SOHU-ZGDM     OS: Microsoft Windows XP Service Pack 2 5.1 Build 2600     Windows directory: C:WINDOWS; Sys

31、tem directory: C:WINDOWSSYSTEM32     Current user: Administrator     Number of processors: 2     Processor: Intel(R) Pentium(R) D CPU 3.00GHz, Frequency: 2992 MHz.     Memory in use: 42%  &#

32、160;  Total Physical Memory: 2,145,427,456 bytes; Available Physical Memory: 1,238,437,888 bytes; Total Virtual Memory: 2,147,352,576 bytes; Available Virtual Memory: 1,806,143,488 bytes; Virtual Memory In Use: 341,209,088 bytes     Microsoft .NET Framework version

33、: v2.0.50727   -Run Mode: Normal      Host Application:      Parameters:      Work Directory:    Process create ID: 5592, Thread ID: 4216, Base address: 0x00400000  421615:18:31:00

34、0 Module loaded: D:TestLuboViewDebugLuboView.exe; Base address: 0x00400000 Size: 3039232 Version:   421615:18:31:000 .          从以上事件信息中可以看到AQTIME在RUN之后,首先会获取如下一些信息:a).当前AQTIME的版本信息:Product:Aqtime;Version:4.92.669.0b).系统工作环

35、境:Host name:SOHU-ZGDMOS:Microsoft Windows XP Service Pack 2 5.1 Build 2600Windows directory: C:WindowsSystem directory:C:WindowsSYSTEM32Current user:AdministratorNumber of Processor:2Processor:Inter® Pentinum® CPU 3.00GHz,Frequency:2992MHz.Memory in use:42%Total Physical Memory:2,145,427,4

36、56 bytes;=2GBAvailable Physical Memory:1,238,437,888 bytes;=1.15GBTotal Virtual Memory:2,147,352,576 bytes;=2GBAvailable Virtual Memory:1,806,143,488 bytes;=1.68GBVirtual Memory in Use: 341,209,088 bytes=325.4MBMicrosoft.NET Framework version:v2.0.50727c).运行参数。因为没有在RUN->parameters下设置相关内容,所以此处为空。但

37、这里有何作用还有待于将来继续实践。d).创建进程,进程ID号为5592,线程ID号为4216,基址为0x00400000e).加载程序:D:TestLuboViewDebugLuboView.exe;基址:0x00400000大小:3039232版本:f).加载动态链接库:C:Windowssystem32ntdll.dll基址:0x7C920000大小:591360版本:5.1.2600.2180.加载其他动态链接库,如kernel32.dll,user32.dll,gdi32.dll6).Monitor面板提示:当前的profiler不支持Monitor面板;Disassem

38、bler面板和Editor面板中会显示汇编代码和VC源代码,前提配置了源代码文件的搜索路径。Details面板,Call Graph面板,Call Tree面板 没有内容。7).LuboView软件加载曲线。8).结束LuboView进程,查看AQtime的统计结果:a).资源文件报告:点击侧边栏Last Results中Classes项从以上分析结果可以看出,一共建立了图标类67个,而有57个没有释放;注册表类创建了376个,有29个没有释放;DC设备类有400个创建,14个没有释放;位图BitMap创建了236个,9个没有释放;PEN类创建了181个,全部释放等等。并且在侧边栏的Last Results

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论