也许你编程的时候很小心,注意不引起内存泄露,例如不要被全局Static的变量引用上,注意Singleton的static引用,注意Event Handler注销,注意IDisposable接口实现,而且正确实现了IDisposable。但或许你还是有内存泄露,为何?因为你的IDisposable接口根本没有被触发!为什么?参考MSDN这个页面的”Dispose method not invoked ”章节。还有其它的内存泄露原因,比如第三方组件或框架,框架本身的内存泄露问题,已经框架本身有LifetimeManagement对象生命周期管理机制。例如我今天要说的MEF引起的内存泄露。
看看MEF的对象生命周期管理机制说明,里面有这么一段话:
Container and parts references
We believe that .Net Garbage Collector is the best thing to rely on for proper clean up. However, we also need to provider a container that has a deterministic behavior. Thus, the container will not hold references to parts it creates unless one of the following is true:
For those cases, a part reference is kept. Combined with the fact that you can have non shared parts and keep requesting those from the container then memory demand can quickly become an issue. In order to mitigate this issue you should rely on one of the following strategies discussed in the next two topics.
原来在MEF中,对象构造策略为Shared,实现IDisposable接口的,和允许recomposition的,都会被全局container引用,也就是说不释放。具体可以去看MEF的源码。
内存分析工具很多,例如Windbg, Redgate ANTS MemoryProfiler, 以及.NET Memory Profiler。我是用的Redgate的,大家可以从下面的图中看到为何这个ViewModel被引用着。根据这个图,去看MEF的源码,就ok了。
1: [PartCreationPolicy(CreationPolicy.NonShared)]
2: [Export(typeof(IMessageSender))]
3: public class SmtpSender : IMessageSender
4: {
5: }
The container will always have the ownership of parts it creates. In other words, the ownership is never transferred to an actor that requested it by using the container instance (directly) or through an import (indirectly).
Imports can also define or constraint the creation policy of parts used to supply the import values. All you have to do is specify the CreationPolicy enum value for RequiredCreationPolicy:
1: [Export]
2: public class Importer
3: {
4: [Import(RequiredCreationPolicy=CreationPolicy.NonShared)]
5: public Dependency Dep { get; set; }
6: }
This is a useful for scenarios where the “shareability” of a part is relevant for the importer. By default, the RequiredCreationPolicy is set to Any, so Shared and NonShared parts can supply the values..
1: var batchProcessorExport = container.GetExport<IBatchProcessor>();
2:
3: var batchProcessor = batchProcessorExport.Value;
4: batchProcessor.Process();
5:
6: container.ReleaseExport(batchProcessorExport);
The figure below depicts an object graph and show what parts would be released (references removed, disposed) and the ones that would be left untouched:
As the root part is just non shared no reference was being kept by the container, so it is basically a no-operation. We proceed traversing the graph checking the exports served to the root part. Dep 1 is both non shared and disposable, so the part is disposed and its reference is removed from the container. The same happens with Dep 2, however, the export used by Dep is left untouched as it is shared – so other parts may be using it.
Note that the implementation traverses the graph in a depth-first way.
1: [Export]
2: public class SomeService : IDisposable
3: {
4: [Import]
5: public ILogger Logger { get; set; }
6:
7: public void Dispose()
8: {
9: Logger.Info("Disposing"); // might throw exception!
10: }
11: }
Using the imported logger instance on your dispose method implementation may be a problem as the implementation of the ILogger contract may also be disposable, and as such may have been disposed already.
1: using System;
2: using System.ComponentModel.Composition;
3: using System.ComponentModel.Composition.Hosting;
4: using System.ComponentModel.Composition.Primitives;
5:
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: var catalog = new AssemblyCatalog(typeof(Program).Assembly);
11: var container = new CompositionContainer(catalog);
12: var root = new Root();
13:
14: // add external part
15: container.ComposeParts(root);
16:
17: // ... use the composed root instance
18:
19: // removes external part
20: batch = new CompositionBatch();
21: batch.RemovePart(root);
22: container.Compose(batch);
23: }
24: }
25:
26: public class Root
27: {
28: [Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
29: public NonSharedDependency Dep { get; set; }
30: }
31:
32: [Export, PartCreationPolicy(CreationPolicy.NonShared)]
33: public class NonSharedDependency : IDisposable
34: {
35: public NonSharedDependency()
36: {
37: }
38:
39: public void Dispose()
40: {
41: Console.WriteLine("Disposed");
42: }
43: }
深入思考和继续阅读
通常.NET程序的内存泄露原因:
有关如何避免.NET程序的内存泄露,请仔细阅读MSDN这两篇文章,详细讲述了<如何检测.NET程序内存泄露>以及<如何写高性能的托管程序>
- Static references
- Event with missing unsubscription
- Static event with missing unsubscription
- Dispose method not invoked
- Incomplete Dispose method
有关.NET的自动内存管理机制、GC机制,垃圾回收原理等深层次内容,请仔细阅读下面的内容:
- How to detect and avoid memory and resources leaks in .NET applications
- Writing High-Performance Managed Applications : A Primer
- 买书《CLR Via C#(3rd Edition)》,里面有《Memory Management》这一章专门讲述了.NET CLR的自动内存管理和垃圾回收机制
- CodeProject上的文章《Memory Management Misconceptions》有助你深入理解Root, Generation 0, 1…