由于C#是一种托管语言,它的垃圾回收机制(GC)是由.net平台负责的,加之C#语言并没有指针,所以我们在使用过程中极少会考虑到内存使用状况以及项目在运行过程中是如何进行内存管理的。所有不愿意去考虑这方面的事情,其实不尽然,很多时候我们都需要考虑C#内存的管理问题,否则会很容易造成内存的泄露问题。
在进程的虚拟内存中,有一个区域称为栈。C#的值类型数据、传递给方法的参数副本都存储在这个栈中。在栈中存储数据时,是从高内存地址向低内存地址填充的。引用类型对象的引用存储在栈中(占4个字节的空间),而它的实际数据存储在主托管堆或大对象堆上。
预防内存泄漏的几种方式:
1、减少定义常量类型和静态类型数据
2、减少使用全局类型定义,在方法中尽量使用局部变量,方法使用后会自动回收(也要尽量避免多次创建,多次创建也会增加内存压力,及时让不再需要的静态字段的引用等于null)
3、文件使用后要及时关闭,释放内存,减少线程占用和产生竞争关系(产生竞争关系时,不能关闭文件)
4、添加析构函数
5、使用using(){}//定义一个范围,在范围结束时处理对象
解决内存占用过多几种方式:
1、使用C#自带的GC.Collect()强制回收内存
2、声明一个析构函数(终结器)
3、使用IDiposable接口
4、实现Dispose模式
5、SetProcessWorkingSetSize清理内存(可以设置定时清理)
GC.Collect()强制回收内存:System.GC类是一个表示垃圾回收器的.NET类,可以调用System.GC.Collect()方法,强迫垃圾回收器在代码的某个地方运行。当代码中有大量的对象刚刚取消引用,就比较适合调用垃圾回收器但不能保证所有未引用的对象都能从堆中删除。垃圾回收器运行时,它实际上会降低程序的性能,因为在它执行期间,将会暂停应用程序的其它所有线程。
析构函数:由于C#使用垃圾回收器的工作方式,无法确定C#对象的析构函数何时执行。定义了析构函数的对象需要经过两次垃圾回收处理才能被销毁(第二次调用析构函数时才真正删除对象),而没有定义析构函数的对象反而只需要一次处理即可删除。如果频繁使用析构函数,而且执行长时间的清理任务,会严重影响性能。
class MyClass
{
~MyClass()
{
}
}
//以下版本是编译析构函数实际调用的等价代码:
protected override void Finalize()
{
try
{ //释放自身资源 }
finally
{ base.Finalize(); }
}
IDiposable接口:推荐通过为类实现System.IDisposable接口,实现Dispose()方法,来替代析构函数。IDisposable接口定义的模式为释放非托管资源提供了确定的机制,并避免了对垃圾回收器依赖的问题。IDisposable接口声明了Dispose()方法,无参数,无返回值。可以为Dispose()方法实现代码来显式地释放由对象直接使用的所有非托管资源,并在所有也实现IDisposable接口的封装对象中调用Dispose()方法。这样,该方法可以可以精确地控制非托管资源的释放。
Person person = null; //假设Person类实现了IDisposable接口
try
{
person = new Person();
}
finally
{
if(person != null)
{
person.Dispose();
}
}
C#提供了using关键字语法,可以确保在实现了IDisposable接口的对象的引用超出作用域时,在该对象上自动调用Dispose()方法,如下:
using ( Person person = new Person() )
{ ..... }
using语句后面是一对"()",其中是引用变量的声明和实例化,该语句是其中的变量放在随后的语句块中,并且在变量超出作用域时,即使抛出异常,也会自动调用Dispose()方法。然后,在需要捕获其它异常时,使用try...finally的方式就会比较清晰。而常常为Dispose()方法定义一个包装方法Close(),这样显得更清晰明了(Close()方法内仅调用Dispose()方法)。为了防止忘记调用Dispose()方法,更保险的做法是同时实现两种机制:即实现IDisposable接口的Dispose()方法,也定义析构函数。
实现Dispose模式:C#程序中的Dispose方法,一旦被调用了该方法的对象,虽然还没有垃圾回收,但实际上已经不能再使用了。
先了解一下C#程序(或者说.NET)中的资源分类。简单的说来,C#中的每一个类型都代表一种资源,而资源又分为两类:
托管资源:由CLR管理分配和释放的资源,即由CLR里new出来的对象;
非托管资源:不受CLR管理的对象,windows内核对象,如文件、数据库连接、套接字、COM对象等;
毫无例外地,如果我们的类型使用到了非托管资源,或者需要显式释放的托管资源,那么,就需要让类型继承接口IDisposable。这相当于是告诉调用者,该类型是需要显式释放资源的,你需要调用我的Dispose方法。
不过,这一切并不这么简单,一个标准的继承了IDisposable接口的类型应该像下面这样去实现。这种实现我们称之为Dispose模式:
public class SampleClass : IDisposable
{
//演示创建一个非托管资源
private IntPtr nativeResource = Marshal.AllocHGlobal(100);
//演示创建一个托管资源
private AnotherResource managedResource = new AnotherResource();
private bool disposed = false;
/// <summary>
/// 实现IDisposable中的Dispose方法,用于手动调用
/// </summary>
public void Dispose()
{
//必须为true
Dispose(true);
//通知垃圾回收机制不再调用终结器(析构器)因为我们已经自己清理了,没必要继续浪费系统资源
//即:从等待终结的Finalize队列中移除this
GC.SuppressFinalize(this);
}
/// <summary>
/// 不是必要的,提供一个Close方法仅仅是为了更符合其他语言(如C++)的规范
/// </summary>
public void Close()
{
Dispose();
}
/// <summary>
/// 必须,以备程序员忘记了显式调用Dispose方法
/// </summary>
~SampleClass()
{
//必须为false,跳过托管资源的清理,只手动清理非托管的资源,垃圾回收器会自动清理托管资源
Dispose(false);
}
/// <summary>
/// 非密封类修饰用protected virtual
/// 密封类修饰用private
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
// 清理托管资源
if (managedResource != null)
{
managedResource.Dispose();
managedResource = null;
}
}
// 清理非托管资源
if (nativeResource != IntPtr.Zero)
{
Marshal.FreeHGlobal(nativeResource);
nativeResource = IntPtr.Zero;
}
//让类型知道自己已经被释放
disposed = true;
}
public void SamplePublicMethod()
{
//确保在执行对象的任何方法之前,该对象可用(未被释放)
if (disposed)
{
throw new ObjectDisposedException("SampleClass", "SampleClass is disposed");
}
//在这里可以使用对象
}
}
在Dispose模式中,几乎每一行都有特殊的含义。在标准的Dispose模式中,我们注意到一个以~开头的方法:
/// <summary>
/// 必须,以备程序员忘记了显式调用Dispose方法
/// </summary>
~SampleClass()
{
//必须为false
Dispose(false);
}
SetProcessWorkingSetSize清理内存:每隔一段时间压缩一次内存,最多压缩到10M。
[DllImport("kernel32.dll")]
public static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);
/// <summary>
/// 刷新内存
/// </summary>
public void FulshMemor()
{
if (System.Environment.OSVersion.Platform == PlatformID.Win32NT)
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
private void Main(object sender, StartupEventArgs e)
{
//每间隔一段时间刷新一次内存
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(10)};
timer.Tick += (s, t) => FulshMemor();
timer.Start();
}
参考: