.Net中的内存泄露

.Net中的内存泄露

说明:

虽然已经有GC垃圾回收器在工作,但是还是会出现内存泄露。

内存碎片

费托管内存泄露比托管内存泄露更加严重。GC可以移动托管内存,为其他对象腾空间。但是非托管内存将永远的卡在它的位置。

GC的工作原理

GC遍历所有GC Root对象,将其标记为不回收;然后GC转到他们引用的所有对象,并将它们标记为不回收。最后GC回收剩下的所有内容。

GC Root

GCRoot包括:正在运行的线程的实时堆栈;静态变量;通过interop传递到COM对象的托管对象。

起因:

  1. 对象资源被引用,但是却未被使用!就因为它们被引用了,所以GC不会清理它们,这样它们将永久占用内存。

例如:注册了一个事件+=;但是不注销-=,就会发生这种情况。这被专业的称为:托管内存泄露。

  1. 当通过某种方式分配非托管内存,就没有GC参与了,因此也不会进行释放。.Net中本身就有很多类会分配非托管内存。几乎所有涉及到流、图形、文件系统或网络调用的操作都会在背后分配非托管内存。这些类通常会提供Dispose()方法来进行内存释放。
  2. .Net提供如Marshal/PInvoke这些特殊的类来实现非托管内存的分配。

八种内存泄露

八种内存泄露情况,前6种是托管内存泄露,后2种是非托管内存泄露。

订阅Events

.Net中的Events因导致内存泄露而出名。起因:订阅事件后,该事件对象将保留对你相应类的引用。除非使用不捕获类成员的匿名方法。

   

假设wifiManager的寿命超过MyClass,那么就已经造成了内存泄露。wifiManager会引用MyClass的任何实例,且GC不会回收它们。

解决方法:

  1. 注销该事件的订阅-=;
  2. 使用弱句柄weak-handler模式。
  3. 使用匿名函数进行订阅,且不要捕获任何类成员。

在匿名方法中捕获类成员

虽然事件机制需要引用一个对象,但是引用对象在匿名方法中捕获类成员时却不明显。

 

 

 

上面的匿名方法中捕获了_id,因此该实例也会被引用。只意味着这个匿名方法还会引用MyClass实例。

 

 

 

可以通过定义局部变量,将_id赋值给localId,此时该匿名方法将不会捕获MyClass了,就可以避免潜在的内存泄露。

静态变量

静态变量及其引用的所有内容都不会被GC回收。

缓存功能

无限次的缓存,最终将耗尽内存。

Var

解决方法:

  1. 删除一段时间未使用的缓存;
  2. 限制缓存大小;
  3. 使用弱引用来保存缓存对象。

错误的WPF绑定

WPF 中的绑定实际上可能会导致内存泄露。通常绑定到DP依赖属性上和实现了INotifyPropertyChanged属性上时是安全的。否则WPF会创建从静态变量到绑定源VM VIEWMODEL的强引用,从而导致内存泄露。

也就是说,定义属性时,要实现INotifyPropertyChanged接口,这会通知WPF不要创建强引用。

另一个是绑定到集合Collection时,也会发生内存泄露。所以要绑定的集合必须要实现INotifyCollectionChanged接口。可以使用现成实现了该接口的observableCollection来避免此问题。

永不终止的线程

GC是不会回收实时堆栈的,实时堆栈中包括正在运行的线程中所有的局部变量和调用堆栈的成员。

出于某种因素,需要创建一个永远运行的线程如Timer,这可能会造成内存泄露。

如果你并没有真正的停止这个timer,那么他会在一个独立的线程中运行,并且timer中用到的各种对象实例是不会被GC回收的。

没有回收非托管内存

非托管内存需要手动编码来回收,而不仅仅是避免不必要的引用。

 

 

 

上述方法使用Marshal.AllocHGlobal方法分配了非托管内存缓冲区。

AllocHGlobal会调用Kernel32.dll中的LocalAlloc方法。如果没有手动调用Marshal.FreeHGlobal来回收内存,则该缓冲区内存将被视为占用了进程的内存堆,从而导致内存泄露。

添加了Dispose方法却不调用它

为了避免这种情况发生,可以使用using语句

Using(var instance=new MyClass){…//do what you want}

编译器会将上面的代码转换为:

 

 

 

使用Dispose Pattern

 

 

 

这种模式可确保即使没有调用Dispose,Dispose也将在实例被垃圾回收时被调用。另一方面,如果调用了Dispose,则finalizer将被抑制(SuppressFinalize)。抑制finalizer很重要,因为finalizer开销很大并且会导致性能问题。

 

然而,dispose-pattern不是万无一失的。如果从未调用Dispose并且由于托管内存泄漏而导致你的类没有被垃圾回收,那么非托管资源也将不会被释放。

posted @ 2020-07-23 15:18  nick_JD  阅读(211)  评论(0编辑  收藏  举报