MEF引起的内存泄露

也许你编程的时候很小心,注意不引起内存泄露,例如不要被全局Static的变量引用上,注意Singleton的static引用,注意Event Handler注销,注意IDisposable接口实现,而且正确实现了IDisposable。但或许你还是有内存泄露,为何?因为你的IDisposable接口根本没有被触发!为什么?参考MSDN这个页面的”Dispose method not invoked ”章节。还有其它的内存泄露原因,比如第三方组件或框架,框架本身的内存泄露问题,已经框架本身有LifetimeManagement对象生命周期管理机制。例如我今天要说的MEF引起的内存泄露。

实现IDisposable接口,却导致没有触发,这是何故?

看看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:

  1. The part is marked as Shared
  2. The part implements IDisposable
  3. One or more imports is configured to allow recomposition

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的源码。

内存泄露分析过程

内存分析工具很多,例如WindbgRedgate ANTS MemoryProfiler, 以及.NET Memory Profiler。我是用的Redgate的,大家可以从下面的图中看到为何这个ViewModel被引用着。根据这个图,去看MEF的源码,就ok了。

MEF Parts Lifetime (zz from: http://mef.codeplex.com/wikipage?title=Parts%20Lifetime)

It’s very important that one understand a parts lifetime within a MEF container and its implications. Given that MEF focuses on open ended applications this becomes especially important as the application authors won’t have control over the set of parts once the app ships and third party extensions come to play. Lifetime can be explained as being the desired “shareability” of a part (transitively, its exports) which translates to the policy that controls when a new part is created as well as when the part will be shut down or disposed.

Shared, Non Shared and ownership

The “shareability” of a part is defined through the CreationPolicy set (class level) using the PartCreationPolicyAttribute. The following values are supported:

  • Shared: the part author is telling MEF that at most one instance of the part may exist per container.
  • NonShared: the part author is telling MEF that each request for exports of the part will be served by a new instance of it.
  • Any or not supplied value: the part author allows the part to be used as either “Shared” or “NonShared”.

The Creation Policy can be defined on a part using the[System.ComponentModel.Composition.PartCreationPolicyAttribute]:

 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..

Disposing the container

A container instance is generally the lifetime holder of parts. Part instances created by the container have their lifetime conditioned to the container’s lifetime. The way to signal the end of the container lifetime is by disposing it. The implications of disposing a container are:

  • Parts that implement IDisposable will have the Dispose method called
  • Reference to parts held on the container will be cleaned up
  • Shared parts will be disposed and cleaned up
  • Lazy exports won’t work after the container is disposed
  • Operations might throw System.ObjectDisposedException

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:

  • The part is marked as Shared
  • The part implements IDisposable
  • One or more imports is configured to allow recomposition

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.

Scoped operations and early reclaim of resources

Some common kinds of applications like web apps and windows services vary greatly from desktop applications. They are more likely to rely on batched and short lived operations. For example, a windows service might watch a directly and once a pre-determined number of file is present, start a batching operation that transforms those files into another format. Web operations may be scoped by per-web-request operations. 
For those scenarios you should either use child containers (explained in the next topic) or release early the object graph. The latter allows the container to dispose and clear out references to non shared parts in the object graph – until it reaches a shared part. 
In order to early release the object graph you need to call the method ReleaseExport exposed by the CompositionContainer:

 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.

Container hierarchies

Another way to approach the same problem is to use container hierarchies. You can create containers and connect them to a parent container, making them child containers. Note that unless you provide a different catalog to the child container, it wouldn’t be of much help as instantiation will continue to happen on the parent. 
Hence, what you should do is either filter the parent catalog based on a criterion that divides the set of parts that should be created on the parent container from those that should be created on the child, or you should specify a completely new catalog that expose a set of parts that should be created on the child container. As the child is expected to be short lived, parts created in it will be released and disposed earlier. 
A common approach is to have Shared parts created on the parent container and Non Shared on the child container. As Shared parts may depend on exports supplied by Non Shared, then the main catalog will have to contain the whole set of parts whereas the child container should have a filtered view of the main catalog with only the non shared parts. 
 
For more information on this topic please check Filtering Catalogs

Disposal ordering

Disposal ordering is not guaranteed in any way. That means that you should not try to use an import in your Dispose method. For example:

 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. 

AddPart/RemovePart

Not every part is created by the container. You can also add and remove parts from it. This process triggers composition and may start creating parts to satisfy dependencies of the part added recursively. When the part added is removed MEF is smart enough to reclaim the resources and dispose the non shared parts used by the part added. 
Note: that MEF will never take ownership of an instance supplied by you, but it does have the ownership of part it creates to satisfy your instance’s imports.

 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程序的内存泄露原因:

  • Static references
  • Event with missing unsubscription
  • Static event with missing unsubscription
  • Dispose method not invoked
  • Incomplete Dispose method

有关如何避免.NET程序的内存泄露,请仔细阅读MSDN这两篇文章,详细讲述了<如何检测.NET程序内存泄露>以及<如何写高性能的托管程序>

有关.NET的自动内存管理机制、GC机制,垃圾回收原理等深层次内容,请仔细阅读下面的内容:

http://www.cnblogs.com/Mainz/archive/2011/09/10/2173172.html

时间: 2024-10-13 16:19:04

MEF引起的内存泄露的相关文章

MEF等Ioc框架引起内存泄露-PartCreationPolicy

对象的创建可以使用new,也可以使用IOC架如:castle.MEF等,IOC创建的对象的生命周期,可能IOC负责管理,使用框架的开发者如果不弄清楚可能会造成内存泄露问题. 这些内存泄露问题并不是IOC框架的bug,只是开发者使用不当或者不注意造成的内存泄露问题. 以MEF为例说明我碰到的两种内存泄露问题. 内存泄露系列阅读提示: 一摸一样的对象图,有时候我们可以认为它是内存泄露,有时候又认为它不是内存泄露,这一切只是由于上下文不同,这一系列文章中ANTS Memoery Profle截图都是有

静态变量导致的内存泄露

public  class MainActivity extends Activity{                private static final String TAG = "MainActivity";             private static Context sContext;                   @Override             protected void onCreate(Bundle savedInstanceState)

android内存优化-Activity, Thread引起的内存泄露0

Android编程中一个共同的困难就是协调Activity的生命周期和长时间运行的任务(task),并且要避免可能的内存泄露.思考下面Activity的代码,在它启动的时候开启一个线程并循环执行任务. 1 /** 2 * 一个展示线程如何在配置变化中存活下来的例子(配置变化会导致创 3 * 建线程的Activity被销毁).代码中的Activity泄露了,因为线程被实 4 * 例为一个匿名类实例,它隐式地持有外部Activity实例,因此阻止Activity 5 * 被回收. 6 */ 7 pu

【C++】小项目——内存泄露检测器

在C++中,指针往往忘记释放.引起内存泄露. 1.内存泄露指: 内存泄漏也称作"存储渗漏",用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元.直到程序结束.(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏. 内存泄漏形象的比喻是"操作系统可提供给所有进程的存储空间正在被某个进程榨干",最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃.所以"内存泄漏"是从操作系统的角度

android 内存泄露调试

一.概述 1 二.Android(Java)中常见的容易引起内存泄漏的不良代码 1 (一) 查询数据库没有关闭游标 2 (二) 构造Adapter时,没有使用缓存的 convertView 3 (三) Bitmap对象不在使用时调用recycle()释放内存 4 (四) 释放对象的引用 4 (五) 其他 5 三.内存监测工具 DDMS --> Heap 5 四.内存分析工具 MAT(Memory Analyzer Tool) 7 (一) 生成.hprof文件 7 (二) 使用MAT导入.hpro

最新版 使用Xcode6.4 和Instruments、Leaks调试解决iOS内存泄露

分析内存泄露 最近用到内存泄露,查看以前的博客,方法不错,但操作时,步骤好多都找不到啦,不知道怎么操作.所以下面做了一个简单的例子 实验的开发环境:XCode 6.4 一.Analyze (shift+command+b) app不crash了,那看看有没有内存泄露.用XCode的Analyze就能分析到哪里有内存泄露 分析之后可以看到: 这里提示alertView没被释放,有内存泄露,那我们释放 [alertView release]; 再分析,这个问题解决了. 二.使用Instruments

内存泄露 Memory Leaks 内存优化【总结】

什么是内存泄露 内存管理一直是Java 所鼓吹的强大优点.开发者只需要简单地创建对象,而Java的垃圾收集器将会自动管理内存空间的分配和释放. 但在很多情况下,事情并不那么简单,在 Java程序中总是会频繁地发生内存泄露(Memory Leaks). 内存泄漏就是:当某些对象不再被应用程序所使用,但是由于仍然被引用而导致垃圾收集器不能释放他们. 或者说是:我们对某一内存空间使用完成后没有释放. 用白话来说就是:该回收的内存没被回收. 要理解这个定义,我们需要理解内存中的对象状态.下图展示了什么是

【转】VLD(Visual LeakDetector)内存泄露库的使用

转载自http://blog.csdn.net/fan_hai_ping/article/details/8023433 VLD简介 由于C/C++语言没有所谓的垃圾收集器,内存的分配和释放都需要程序员自己来控制,这会给C/C++程序员带来一定的困难.当您的程序越来越复杂时,它的内存管理也会变得越来越困难.内存泄漏.内存越界是最常见的内存问题之一. 内存泄漏如果不是很严重的话,在短时间内对程序不会造成太大的影响,而且在进程终止的时候,所有分配的内存都会释放掉.但是对于长时间运行的程序,其破坏力是

内存溢出和内存泄露的区别

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出. 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. memory leak会最终会导致out of memory! 内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产