您的位置:首页 > 其它

[Silverlight入门系列]MEF引起的内存泄露

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

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

看看MEF的对象生命周期管理机制说明,里面有这么一段话:

Containerandpartsreferences

Webelievethat.NetGarbageCollectoristhebestthingtorelyonforpropercleanup.However,wealsoneedtoprovideracontainerthathasadeterministicbehavior.Thus,thecontainerwillnotholdreferencestopartsitcreatesunlessoneofthefollowingistrue:

ThepartismarkedasShared

ThepartimplementsIDisposable

Oneormoreimportsisconfiguredtoallowrecomposition

Forthosecases,apartreferenceiskept.Combinedwiththefactthatyoucanhavenonsharedpartsandkeeprequestingthosefromthecontainerthenmemorydemandcanquicklybecomeanissue.Inordertomitigatethisissueyoushouldrelyononeofthefollowingstrategiesdiscussedinthenexttwotopics.

原来在MEF中,对象构造策略为Shared,实现IDisposable接口的,和允许recomposition的,都会被全局container引用,也就是说不释放。具体可以去看MEF的源码。

内存泄露分析过程

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



MEFPartsLifetime(zzfrom:http://mef.codeplex.com/wikipage?title=Parts%20Lifetime)

It’sveryimportantthatoneunderstandapartslifetimewithinaMEFcontaineranditsimplications.GiventhatMEFfocusesonopenendedapplicationsthisbecomesespeciallyimportantastheapplicationauthorswon’thavecontroloverthesetofpartsoncetheappshipsandthirdpartyextensionscometoplay.Lifetimecanbeexplainedasbeingthedesired“shareability”ofapart(transitively,itsexports)whichtranslatestothepolicythatcontrolswhenanewpartiscreatedaswellaswhenthepartwillbeshutdownordisposed.

Shared,NonSharedandownership

The“shareability”ofapartisdefinedthroughtheCreationPolicyset(classlevel)usingthePartCreationPolicyAttribute.Thefollowingvaluesaresupported:

Shared:thepartauthoristellingMEFthatatmostoneinstanceofthepartmayexistpercontainer.

NonShared:thepartauthoristellingMEFthateachrequestforexportsofthepartwillbeservedbyanewinstanceofit.

Anyornotsuppliedvalue:thepartauthorallowstheparttobeusedaseither“Shared”or“NonShared”.

TheCreationPolicycanbedefinedonapartusingthe[System.ComponentModel.Composition.PartCreationPolicyAttribute]:
[code][PartCreationPolicy(CreationPolicy.NonShared)]


[Export(typeof(IMessageSender))]


publicclassSmtpSender:IMessageSender


{


}

[/code]

Thecontainerwillalwayshavetheownershipofpartsitcreates.Inotherwords,theownershipisnevertransferredtoanactorthatrequesteditbyusingthecontainerinstance(directly)orthroughanimport(indirectly).

Importscanalsodefineorconstraintthecreationpolicyofpartsusedtosupplytheimportvalues.AllyouhavetodoisspecifytheCreationPolicyenumvalueforRequiredCreationPolicy:

[code][Export]


publicclassImporter


{


[Import(RequiredCreationPolicy=CreationPolicy.NonShared)]


publicDependencyDep{get;set;}


}

[/code]

Thisisausefulforscenarioswherethe“shareability”ofapartisrelevantfortheimporter.Bydefault,theRequiredCreationPolicyissettoAny,soSharedandNonSharedpartscansupplythevalues..

Disposingthecontainer

Acontainerinstanceisgenerallythelifetimeholderofparts.Partinstancescreatedbythecontainerhavetheirlifetimeconditionedtothecontainer’slifetime.Thewaytosignaltheendofthecontainerlifetimeisbydisposingit.Theimplicationsofdisposingacontainerare:

PartsthatimplementIDisposablewillhavetheDisposemethodcalled

Referencetopartsheldonthecontainerwillbecleanedup

Sharedpartswillbedisposedandcleanedup

Lazyexportswon’tworkafterthecontainerisdisposed

OperationsmightthrowSystem.ObjectDisposedException

Containerandpartsreferences

Webelievethat.NetGarbageCollectoristhebestthingtorelyonforpropercleanup.However,wealsoneedtoprovideracontainerthathasadeterministicbehavior.Thus,thecontainerwillnotholdreferencestopartsitcreatesunlessoneofthefollowingistrue:

ThepartismarkedasShared

ThepartimplementsIDisposable

Oneormoreimportsisconfiguredtoallowrecomposition

Forthosecases,apartreferenceiskept.Combinedwiththefactthatyoucanhavenonsharedpartsandkeeprequestingthosefromthecontainerthenmemorydemandcanquicklybecomeanissue.Inordertomitigatethisissueyoushouldrelyononeofthefollowingstrategiesdiscussedinthenexttwotopics.

Scopedoperationsandearlyreclaimofresources

Somecommonkindsofapplicationslikewebappsandwindowsservicesvarygreatlyfromdesktopapplications.Theyaremorelikelytorelyonbatchedandshortlivedoperations.Forexample,awindowsservicemightwatchadirectlyandonceapre-determinednumberoffileispresent,startabatchingoperationthattransformsthosefilesintoanotherformat.Weboperationsmaybescopedbyper-web-requestoperations.

Forthosescenariosyoushouldeitherusechildcontainers(explainedinthenexttopic)orreleaseearlytheobjectgraph.Thelatterallowsthecontainertodisposeandclearoutreferencestononsharedpartsintheobjectgraph–untilitreachesasharedpart.

InordertoearlyreleasetheobjectgraphyouneedtocallthemethodReleaseExportexposedbytheCompositionContainer:
[code]varbatchProcessorExport=container.GetExport<IBatchProcessor>();




varbatchProcessor=batchProcessorExport.Value;


batchProcessor.Process();




container.ReleaseExport(batchProcessorExport);

[/code]


Thefigurebelowdepictsanobjectgraphandshowwhatpartswouldbereleased(referencesremoved,disposed)andtheonesthatwouldbeleftuntouched:



Astherootpartisjustnonsharednoreferencewasbeingkeptbythecontainer,soitisbasicallyano-operation.Weproceedtraversingthegraphcheckingtheexportsservedtotherootpart.Dep1isbothnonsharedanddisposable,sothepartisdisposedanditsreferenceisremovedfromthecontainer.ThesamehappenswithDep2,however,theexportusedbyDepisleftuntouchedasitisshared–sootherpartsmaybeusingit.

Notethattheimplementationtraversesthegraphinadepth-firstway.

Containerhierarchies

Anotherwaytoapproachthesameproblemistousecontainerhierarchies.Youcancreatecontainersandconnectthemtoaparentcontainer,makingthemchildcontainers.Notethatunlessyouprovideadifferentcatalogtothechildcontainer,itwouldn’tbeofmuchhelpasinstantiationwillcontinuetohappenontheparent.

Hence,whatyoushoulddoiseitherfiltertheparentcatalogbasedonacriterionthatdividesthesetofpartsthatshouldbecreatedontheparentcontainerfromthosethatshouldbecreatedonthechild,oryoushouldspecifyacompletelynewcatalogthatexposeasetofpartsthatshouldbecreatedonthechildcontainer.Asthechildisexpectedtobeshortlived,partscreatedinitwillbereleasedanddisposedearlier.

AcommonapproachistohaveSharedpartscreatedontheparentcontainerandNonSharedonthechildcontainer.AsSharedpartsmaydependonexportssuppliedbyNonShared,thenthemaincatalogwillhavetocontainthewholesetofpartswhereasthechildcontainershouldhaveafilteredviewofthemaincatalogwithonlythenonsharedparts.



FormoreinformationonthistopicpleasecheckFilteringCatalogs

Disposalordering

Disposalorderingisnotguaranteedinanyway.ThatmeansthatyoushouldnottrytouseanimportinyourDisposemethod.Forexample:
[code][Export]


publicclassSomeService:IDisposable


{


[Import]


publicILoggerLogger{get;set;}




publicvoidDispose()


{


Logger.Info("Disposing");//mightthrowexception!


}


}

[/code]

UsingtheimportedloggerinstanceonyourdisposemethodimplementationmaybeaproblemastheimplementationoftheILoggercontractmayalsobedisposable,andassuchmayhavebeendisposedalready.

AddPart/RemovePart

Noteverypartiscreatedbythecontainer.Youcanalsoaddandremovepartsfromit.Thisprocesstriggerscompositionandmaystartcreatingpartstosatisfydependenciesofthepartaddedrecursively.WhenthepartaddedisremovedMEFissmartenoughtoreclaimtheresourcesanddisposethenonsharedpartsusedbythepartadded.

Note:thatMEFwillnevertakeownershipofaninstancesuppliedbyyou,butitdoeshavetheownershipofpartitcreatestosatisfyyourinstance’simports.

[code]usingSystem;


usingSystem.ComponentModel.Composition;


usingSystem.ComponentModel.Composition.Hosting;


usingSystem.ComponentModel.Composition.Primitives;




classProgram


{


staticvoidMain(string[]args)


{


varcatalog=newAssemblyCatalog(typeof(Program).Assembly);


varcontainer=newCompositionContainer(catalog);


varroot=newRoot();




//addexternalpart


container.ComposeParts(root);




//...usethecomposedrootinstance




//removesexternalpart


batch=newCompositionBatch();


batch.RemovePart(root);


container.Compose(batch);


}


}




publicclassRoot


{


[Import(RequiredCreationPolicy=CreationPolicy.NonShared)]


publicNonSharedDependencyDep{get;set;}


}




[Export,PartCreationPolicy(CreationPolicy.NonShared)]


publicclassNonSharedDependency:IDisposable


{


publicNonSharedDependency()


{


}




publicvoidDispose()


{


Console.WriteLine("Disposed");


}


}

[/code]



深入思考和继续阅读

通常.NET程序的内存泄露原因:

Staticreferences
Eventwithmissingunsubscription
Staticeventwithmissingunsubscription
Disposemethodnotinvoked
IncompleteDisposemethod
有关如何避免.NET程序的内存泄露,请仔细阅读MSDN这两篇文章,详细讲述了<如何检测.NET程序内存泄露>以及<如何写高性能的托管程序>
Howtodetectandavoidmemoryandresourcesleaksin.NETapplications
WritingHigh-PerformanceManagedApplications:APrimer
有关.NET的自动内存管理机制、GC机制,垃圾回收原理等深层次内容,请仔细阅读下面的内容:
买书《CLRViaC#(3rdEdition)》,里面有《MemoryManagement》这一章专门讲述了.NETCLR的自动内存管理和垃圾回收机制
CodeProject上的文章《MemoryManagementMisconceptions》有助你深入理解Root,Generation0,1…

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: