浅析c#内存泄漏
一直以来都对内存泄露和内存溢出理解的不是很深刻。在网上看到了几篇文章,于是整理了一下自己对内存泄露和内存溢出的理解。
一.概念
内存溢出:指程序在运行的过程中,程序对内存的需求超过了超过了计算机分配给程序的内存,从而造成“Out of memory”之类的错误,使程序不能正常运行。
造成内存溢出有几种情况: 1.计算机本身的内存小,当同时运行多个软件时,计算机得内存不够用从而造成内存溢出。对于这种情况,只能增加计算机内存来解决。 2.软件程序的问题,程序在运行时没能及时释放不用的内存,造成使用的内存越来越大从而造成内存溢出。对于这种情况,可以修改程序的代码来解决。
内存泄露:内存泄漏指由于疏忽或错误造成程序不能释放或不能及时释放已经不再使用的内存的情况,是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存不能回收和不能及时回收。当程序不能释放的内存越来越多是就会造成程序的性能下降或出现内存溢出的错误。
二、内存泄露检测工具:
1. SciTech Software AB .NET Memory Profiler-找到内存泄漏并优化内存使用针对C#,VB.Net,或其它.Net程序。
2. YourKit .NET & Java Profiler-业界领先的Java和.NET程序性能分析工具。
3. AutomatedQA AQTime-AutomatedQA的获奖产品performance profiling和memory debugging工具集的下一代替换产品,支持Microsoft, Borland, Intel, Compaq 和 GNU编译器。可以为.NET和Windows程序生成全面细致的报告,从而帮助您轻松隔离并排除代码中含有的性能问题和内存/资源泄露问题。支持.Net 1.0,1.1,2.0,3.0和Windows 32/64位应用程序。
4. JavaScript Memory Leak Detector-微软全球产品开发欧洲团队(Global Product Development- Europe team, GPDE) 发布的一款调试工具,用来探测JavaScript代码中的内存泄漏,运行为IE系列的一个插件。
5.使用LoadRunner,使用方法http://www.cnblogs.com/mayingbao/archive/2007/12/20/1006818.html
6.使用 .Net Memory Profiler 工具,使用方法见:http://lzy.iteye.com/blog/344317
7.在单元测试时,在代码中检测,如.net 下 使用Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));代码可以查看当前使用的内存。
二、导致内存泄露的常见情况及解决方法:
1.未退订的事件
是否没有手动注销事件就会造成内存泄露,我们先看这个问题
- class TestClassHasEvent
- {
- public delegate void TestEventHandler(object sender, EventArgs e);
- public event TestEventHandler YourEvent;
- protected void OnYourEvent(EventArgs e)
- {
- if (YourEvent != null) YourEvent(this, e);
- }
- }
- class TestListener
- {
- byte[] m_ExtraMemory = new byte[1000000];
- private TestClassHasEvent _inject;
- public TestListener(TestClassHasEvent inject)
- {
- _inject = inject;
- _inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
- }
- void _inject_YourEvent(object sender, EventArgs e)
- {
- }
- }
- class Program
- {
- static void DisplayMemory()
- {
- Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
- }
- static void Main()
- {
- DisplayMemory();
- Console.WriteLine();
- for (int i = 0; i < 5; i++)
- {
- Console.WriteLine("--- New Listener #{0} ---", i + 1);
- var listener = new TestListener(new TestClassHasEvent());
- ////listener = null; //可有可无
- GC.Collect();
- GC.WaitForPendingFinalizers();
- GC.Collect();
- DisplayMemory();
- }
- Console.Read();
- }
- }
class TestClassHasEvent
{
public delegate void TestEventHandler(object sender, EventArgs e);
public event TestEventHandler YourEvent;
protected void OnYourEvent(EventArgs e)
{
if (YourEvent != null) YourEvent(this, e);
}
} class TestListener
{
byte[] m_ExtraMemory = new byte[1000000]; private TestClassHasEvent _inject; public TestListener(TestClassHasEvent inject)
{
_inject = inject;
_inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
} void _inject_YourEvent(object sender, EventArgs e)
{ }
} class Program
{
static void DisplayMemory()
{
Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
} static void Main()
{
DisplayMemory();
Console.WriteLine();
for (int i = 0; i < 5; i++)
{
Console.WriteLine("--- New Listener #{0} ---", i + 1); var listener = new TestListener(new TestClassHasEvent());
////listener = null; //可有可无 GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
DisplayMemory(); }
Console.Read();
}
}
运行结果:
我们来改一行代码:
把下面这段:
- public TestListener(TestClassHasEvent inject)
- {
- _inject = inject;
- _inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
- }
public TestListener(TestClassHasEvent inject)
{
_inject = inject;
_inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
}
改成:
- public TestListener(TestClassHasEvent inject)
- {
- SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
- }
- void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
- {
- }
public TestListener(TestClassHasEvent inject)
{
SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
} void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{ }
看看运行结果:
内存泄露了
加个Dispose手动注销事件,然后使用Using关键字,就没有问题了
- class TestListener : IDisposable
- {
- byte[] m_ExtraMemory = new byte[1000000];
- private TestClassHasEvent _inject;
- public TestListener(TestClassHasEvent inject)
- {
- SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
- }
- void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
- {
- }
- #region IDisposable Members
- public void Dispose()
- {
- SystemEvents.DisplaySettingsChanged -= new EventHandler(SystemEvents_DisplaySettingsChanged);
- }
- #endregion
- }
- class Program
- {
- static void DisplayMemory()
- {
- Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
- }
- static void Main()
- {
- DisplayMemory();
- Console.WriteLine();
- for (int i = 0; i < 5; i++)
- {
- Console.WriteLine("--- New Listener #{0} ---", i + 1);
- using (var listener = new TestListener(new TestClassHasEvent()))
- {
- //do something
- }
- GC.Collect();
- GC.WaitForPendingFinalizers();
- GC.Collect();
- DisplayMemory();
- }
- Console.Read();
- }
- }
class TestListener : IDisposable
{
byte[] m_ExtraMemory = new byte[1000000]; private TestClassHasEvent _inject; public TestListener(TestClassHasEvent inject)
{
SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
} void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{ } #region IDisposable Members public void Dispose()
{
SystemEvents.DisplaySettingsChanged -= new EventHandler(SystemEvents_DisplaySettingsChanged);
} #endregion
} class Program
{
static void DisplayMemory()
{
Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
} static void Main()
{
DisplayMemory();
Console.WriteLine();
for (int i = 0; i < 5; i++)
{
Console.WriteLine("--- New Listener #{0} ---", i + 1); using (var listener = new TestListener(new TestClassHasEvent()))
{
//do something
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
DisplayMemory(); }
Console.Read();
}
}
上面两个例子一个内存泄露,一个没有内存泄露,我想你应该知道原因了,根本区别在于后者有个SystemEvents.DisplaySettingsChanged事件,这个事件是静态Static事件,所以绑定到这个事件上的对象都不会被释放
- // Type: Microsoft.Win32.SystemEvents
- // Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- // Assembly location: C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.dll
- using System;
- using System.ComponentModel;
- namespace Microsoft.Win32
- {
- public sealed class SystemEvents
- {
- public static IntPtr CreateTimer(int interval);
- public static void InvokeOnEventsThread(Delegate method);
- public static void KillTimer(IntPtr timerId);
- public static event EventHandler DisplaySettingsChanging;
- public static event EventHandler DisplaySettingsChanged;
- public static event EventHandler EventsThreadShutdown;
- public static event EventHandler InstalledFontsChanged;
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Obsolete("This event has been deprecated. http://go.microsoft.com/fwlink/?linkid=14202")]
- [Browsable(false)]
- public static event EventHandler LowMemory;
- public static event EventHandler PaletteChanged;
- public static event PowerModeChangedEventHandler PowerModeChanged;
- public static event SessionEndedEventHandler SessionEnded;
- public static event SessionEndingEventHandler SessionEnding;
- public static event SessionSwitchEventHandler SessionSwitch;
- public static event EventHandler TimeChanged;
- public static event TimerElapsedEventHandler TimerElapsed;
- public static event UserPreferenceChangedEventHandler UserPreferenceChanged;
- public static event UserPreferenceChangingEventHandler UserPreferenceChanging;
- }
- }
// Type: Microsoft.Win32.SystemEvents
// Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.dll using System;
using System.ComponentModel; namespace Microsoft.Win32
{
public sealed class SystemEvents
{
public static IntPtr CreateTimer(int interval);
public static void InvokeOnEventsThread(Delegate method);
public static void KillTimer(IntPtr timerId);
public static event EventHandler DisplaySettingsChanging;
public static event EventHandler DisplaySettingsChanged;
public static event EventHandler EventsThreadShutdown;
public static event EventHandler InstalledFontsChanged; [EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This event has been deprecated. http://go.microsoft.com/fwlink/?linkid=14202")]
[Browsable(false)]
public static event EventHandler LowMemory; public static event EventHandler PaletteChanged;
public static event PowerModeChangedEventHandler PowerModeChanged;
public static event SessionEndedEventHandler SessionEnded;
public static event SessionEndingEventHandler SessionEnding;
public static event SessionSwitchEventHandler SessionSwitch;
public static event EventHandler TimeChanged;
public static event TimerElapsedEventHandler TimerElapsed;
public static event UserPreferenceChangedEventHandler UserPreferenceChanged;
public static event UserPreferenceChangingEventHandler UserPreferenceChanging;
}
}
注意Static,注意Singleton 这种static的东西生命周期很长,永远不会被GC回收,一旦被他给引用上了,那就不可能释放了。上面的例子就是SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);那就意味着这个类被SystemEvents.DisplaySettingsChanged 引用了,通过它的函数。另外一个要注意的是Singleton单例模式实现的类,他们也是static的生命周期很长,要注意引用链,你的类是否被它引用上,如果在它的引用链上,就内存泄露了。
另外还有注意程序运行期间不会释放的对象的事件
还有一种情况,既不是你的对象被static对象而不能释放,也不是Singleton,而是你的对象被一个永远不释放的对象引用着,这个对象或许不是static的。这种类型很多,比如你的界面有个MainForm,嘿嘿,这个MainForm永远不会关闭和释放的,被它引用了那就不会释放了。看个例子:
MainForm里面有个public event,MainForm里面打开Form2,然后关闭,看看Form2能不能释放:
- public partial class MainForm : Form
- {
- public event PropertyChangedEventHandler PropertyChanged;
- protected virtual void OnPropertyChanged(string propertyName)
- {
- PropertyChangedEventHandler handler = PropertyChanged;
- if (handler != null)
- handler(this, new PropertyChangedEventArgs(propertyName));
- }
- public MainForm()
- {
- InitializeComponent();
- }
- private void button1_Click(object sender, EventArgs e)
- {
- Form2 frm = new Form2();
- this.PropertyChanged += frm.frm_PropertyChanged;
- //MainForm referenced form2, because main form is not released, therefore form2 will not released.
- DialogResult d = frm.ShowDialog();
- GC.Collect();
- ShowTotalMemory();
- }
- private void ShowTotalMemory()
- {
- this.listBox1.Items.Add(string.Format("Memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true)));
- }
- }
public partial class MainForm : Form
{
public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged; if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
} public MainForm()
{
InitializeComponent();
} private void button1_Click(object sender, EventArgs e)
{
Form2 frm = new Form2(); this.PropertyChanged += frm.frm_PropertyChanged;
//MainForm referenced form2, because main form is not released, therefore form2 will not released. DialogResult d = frm.ShowDialog(); GC.Collect();
ShowTotalMemory(); } private void ShowTotalMemory()
{
this.listBox1.Items.Add(string.Format("Memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true)));
}
}
Form2里面有个函数:
- public partial class Form2 : Form
- {
- public Form2()
- {
- InitializeComponent();
- }
- public void frm_PropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- }
- }
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
public void frm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{ }
}
所以这种情况下,你的Event handler没有手动注销,那就肯定内存泄露了。
2.静态变量
静态变量中的成员所占的内存不果不手动处理是不会释放内存的,单态模式的对象也是静态的,所以需要特别注意。因为静态对象中的成员所占的内存不会释放,如果此成员是以个对象,同时此对象中的成员所占的内存也不会释放,以此类推,如果此对象很复杂,而且是静态的就很容易造成内存泄露。
3.非托管资源
因为非托管资源所占的内存不能自动回收,所以使用后必须手动回收,否则程序运行多次很容易造成内存泄露
4.Dispose方法没被调用,或Dispose方法没有处理对象的释放。这样也会造成内存泄露
5.当一个查询语句查询出来的数据量很大,达到几百万条数据时存放到datatable 或dataset中也会造成内存溢出,这是可以采用分页查询等其他方法来解决
浅析c#内存泄漏的更多相关文章
- 浅析java内存管理机制
内存管理是计算机编程中的一个重要问题,一般来说,内存管理主要包括内存分配和内存回收两个部分.不同的编程语言有不同的内存管理机制,本文在对比C++和Java语言内存管理机制的不同的基础上,浅析java中 ...
- java的GC与内存泄漏
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- 一次"内存泄漏"引发的血案
本文转载自一次"内存泄漏"引发的血案 导语 2017年末,手Q春节红包项目期间,为保障活动期间服务正常稳定,我对性能不佳的Ark Server进行了改造和重写.重编发布一段时间后, ...
- C# 内存泄漏之 Internal 关键词代表什么?
一:背景 1. 背景 前段时间有位朋友咨询说他的程序出现了非托管内存泄漏,说里面有很多的 HEAP_BLOCK 都被标记成了 Internal 状态,而且 size 都很大, 让我帮忙看下怎么回事? ...
- 【知识必备】内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye
一.写在前面 对于C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针:而对于java来说,就是new出来的Object放在Heap上无法被GC回收:而这里就把我之前的一篇内存泄漏的总 ...
- Android性能优化之利用Rxlifecycle解决RxJava内存泄漏
前言: 其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所有想着查找一下资料学 ...
- Android性能优化之利用LeakCanary检测内存泄漏及解决办法
前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...
- C++的内存泄漏检测
C++大量的手动分配.回收内存是存在风险的,也许一个函数中一小块内存泄漏被重复放大之后,最后掏空内存. 这里介绍一种在debug模式下测试内存泄漏的方法. 首先在文件的开头以确定的顺序写下这段代码: ...
- 使用 Android Studio 检测内存泄漏与解决内存泄漏问题
本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...
随机推荐
- runtime 初入
一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C语言,函数的调用在编译的时候会决定调用哪个函数. 对于OC的函数,属于 ...
- android库站点
图片加载库picasso:让本地图片.网络图片的加载变得如此简单. 引导页面的开源框架 guideshow自带小动画,但sdk要求18+ AppIntro界面很简约漂亮 circleindicator ...
- intent打开第三方应用
有时候我们会有在自己的应用中进入另一个第三方应用的需求,首先要知道第三方应用的包名和主activity,很简单遍历一下所有的应用就能拿到了. private void go2App(String pa ...
- SOJ 1717 Computer (单机任务调度)
一.题目描述 Constraints :Time Limit: 2 secs, Memory Limit: 32 MB Description: We often hear that computer ...
- [读书笔记]自动装箱的陷阱以及==与equals
先看一段代码,来自周志明的<深入理解Java虚拟机>. Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Intege ...
- SqlServer 行转列(统计某年一到十二个月数据总和)
select * from( select sum(case MONTH(purchase_date) when '1' then SumMoney else 0 end) as January ...
- Emgu学习手册
作为opencv的c#封装库.emgu可以满足基本的图像处理功能,经过测试,效果还可以,主要用于windows窗体应用程序的开发,或者wpf,你可以用来做ocr,也可以用来做人脸识别或者可以用来做定位 ...
- js-PC版监听键盘大小写事件
//获取键盘按键事件,可以使用keyup. //问题:获取到键盘的按下Caps lock键时,不能知道当前状态是大写.还是小写状态. //解决: 设置一个全局判断大小写状态的 标志:isCapital ...
- jquery常用选择器和常用方法
基本选择器 $(”#myDiv”) //匹配唯一的具有此id值的元素 $(”div”) //匹配指定名称的所有元素 $(”.myClass”) //匹配具有此class样式值的所有元素 $(”*”) ...
- Codeforces Round #172 (Div. 2)
A. Word Capitalization 模拟. B. Nearest Fraction 枚举. C. Rectangle Puzzle 求出两个矩形的点,套简单多边形的面积交板子. D. Max ...