.net的托管并不是万能的,对于有些资源如窗体、文件、位图、数据库连接都需要相应的手动回收。
.net使用的托管内存,值类型存储在堆栈上,引用类型存储在托管堆上,由GC负责垃圾回收。而COM对象使用的是内置内存,因此无法托管,需要手动释放内存。但是COM的内存管理机制是怎么样的呢?.net环境下调用COM组件,COM对象的垃圾回收应该如何进行呢,一般原则又是什么呢?这些我都不知道。
于是在ArcGIS Engine论坛上发帖求助,也没有人回答。现在把遇到的问题重新整理一下,发到博客园,希望能够得到解答。不管是自己还是别人帮助。也记录这个过程。一共发了三个帖子,如下:
1.AE进行二次开发中,COM对象的垃圾收集问题应该如何进行?
问:AE进行二次开发中,经常忽略的垃圾收集问题,AE是COM对象,垃圾收集问题应该如何进行?一般的for循环中的COM对象在什么时候释放了,还是自动释放?其他的情况应该注意哪些? |
回答:我一般用Marshal.ReleaseComObject(XXX)在循环后释放部分com对象。好像还有方法可以强制进行垃圾回收,不过那样做代价太大 |
问:先前在贴吧中问的关于COM对象的回收问题,现在让我感觉更加迷惘了!自己做了一下测试,程序中只有一个Form窗体,窗体中布局了一个MapControl和LisenceControl和一个Botton按钮,按钮的事件代码如下: private void btnAddMap_Click(object sender, EventArgs e) { IMapDocument pMapDoc = new MapDocumentClass(); //MapDocumentClass是COM对象,后记:此说法错误 pMapDoc.Open("D:\\演示数据\\专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); IMap pMap = pMapDoc.get_Map(0);// 返回COM对象MapClass的接口IMap 后记:COM对象Map对应RCW的接口IMAP axMapControl1.Map = pMap; Marshal.ReleaseComObject(pMapDoc); //(1) Marshal.ReleaseComObject(pMap); //(2) GC.Collect(); //(3) axMapControl1.Refresh(); } (1)(2)(3)句代码做如下组合,a类三句代码都不添加,b添加(1)(2),c添加(3),d添加(1)(2)(3)句代码。对每种组合重复点击button按钮,这样Map就会重复加载,每次都会有MapDocumentClass和pMapDoc.get_Map(0)产生新的COM对象,第一次加载内存增长比较多可以理解。a类加载到21次左右,程序弹出错误如图(1);b种不弹出错误,点击40次没有报错,此时内存还是不断往上涨的;C中内存没有b中增长的那么迅速,在点击20次左右的时候似乎基本稳定了;d中内存增长最小,在20次左右也基本恒定了。我的数据中主要是一些Tin和一个GeodataBase中的一些要素(图2),整个数据集大小5M左右,地图文档749K;
这样看来只有是New的COM对象,只要没有继续引用就应该释放比较好啊!
问:依然是前面的程序,通过如下两句返回当前COM对象释放一次后的引用数n,m。 int n= Marshal.ReleaseComObject(pMapDoc);int m = Marshal.ReleaseComObject(pAct);都调用一次,返回值为0:1,加一句m= Marshal.ReleaseComObject(pMap);返回值为0:0接口变量赋值和接口跳转不影响执行结果。也就是说这两者都不影响引用计数喽?IMap pMap2 = pMap;(分别添加)IActiveView pAct = pMap as IActiveView;(分别添加)private void btnAddMap_Click(object sender, EventArgs e) { IMapDocument pMapDoc = new MapDocumentClass();//MapDocumentClass是COM对象 pMapDoc.Open("D:\\红石岩演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); IMap pMap = pMapDoc.get_Map(0);//返回COM对象MapClass的接口IMap //IMap pMap2 = pMap; //IActiveView pAct = pMap as IActiveView; axMapControl1.Map = pMap; int n= Marshal.ReleaseComObject(pMapDoc); int m= Marshal.ReleaseComObject(pMap); //int m = Marshal.ReleaseComObject(pAct); //int m= Marshal.ReleaseComObject(pMap2); // m = Marshal.ReleaseComObject(pMap2); GC.Collect(); axMapControl1.Refresh(); MessageBox.Show(n.ToString() + ":" + m.ToString()); }采用如下代码:IMapDocument pMapDoc = new MapDocumentClass(); pMapDoc.Open("D:\\演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); int i = Marshal.ReleaseComObject(pMapDoc); pMapDoc = new MapDocumentClass(); pMapDoc.Open("D:\\演示数据[url=file://\\Data\\position.mxd]\\Data\\position.mxd[/url]", ""); IMap pMap = pMapDoc.get_Map(0); axMapControl1.Map = pMap; int n= Marshal.ReleaseComObject(pMapDoc); int m = Marshal.ReleaseComObject(pMap); GC.Collect(); axMapControl1.Refresh(); MessageBox.Show(i.ToString()+":"+n.ToString() + ":" + m.ToString());将Button中的代码改成这样,返回值0:0:1,i值是对第一次定义的MapDocumentClass对象计数减1 |
以上测试了重复加载Map,出现的一些症状。后来发现自己忽略了MapDocument的Colse方法。这个方法是不是释放文件资源呢?
继续测试如下代码:private void btnAddMap_Click(object sender, EventArgs e) { IMapDocument pMapDoc = new MapDocumentClass(); pMapDoc.Open("D:\\演示数据\\专题[url=file://\\Untitled.mxd]\\Untitled.mxd]\\Untitled.mxd]\\Untitled.mxd[/url]", ""); IMap pMap = pMapDoc.get_Map(0); pMapDoc.Close(); axMapControl1.Map = pMap; int n = Marshal.ReleaseComObject(pMapDoc); int m = Marshal.ReleaseComObject(pMap); axMapControl1.Refresh(); }发现内存还是一直上涨,但是没有出现资源不足的错误。但是帮助文档的解释是对MapDocument对象进行重置。1. 测试下面的代码: IMapDocument pMapDoc = new MapDocumentClass(); pMapDoc.Open("D:\\演示数据\\专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); IMap pMap = pMapDoc.get_Map(0); pMapDoc.Close(); int n = Marshal.ReleaseComObject(pMapDoc); int m = Marshal.ReleaseComObject(pMap); MessageBox.Show( n.ToString() + ":" + m.ToString());返回值为:0:0,可以看出IMap pMap = pMapDoc.get_Map(0);增加了一次对Map的引用计数。2. 下面的代码: IMapDocument pMapDoc = new MapDocumentClass(); pMapDoc.Open("D:\\演示数据\\地质专题\\Untitled.mxd", ""); IMap pMap = pMapDoc.get_Map(0); pMapDoc.Close(); axMapControl1.Map = pMap; int n = Marshal.ReleaseComObject(pMapDoc); int m = Marshal.ReleaseComObject(pMap); MessageBox.Show( n.ToString() + ":" + m.ToString());返回值为0:1,可以看出 axMapControl1.Map = pMap;增加一次对Map对象的引用计数。3.下面代码: IMapDocument pMapDoc = new MapDocumentClass(); pMapDoc.Open("D:\\演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); IMap pMap = pMapDoc.get_Map(0); pMapDoc.Close(); axMapControl1.Map = pMap; int n = Marshal.ReleaseComObject(pMapDoc);(1) int m = Marshal.ReleaseComObject(pMap);(2) m = Marshal.ReleaseComObject(pMap); (2) GC.Collect();(3) axMapControl1.Refresh();对(1)(2)(2)(3)执行组合,(1)(2),(1)(2)(2),(1)(2)(2)(3)三种组合,发现最后一种效果很明显,内存不会持续增长,会呈现波动。但是前两种内存会持续增长。难道强制GC清理的效果这么明显?产生的内存增长不是因为COM对象、Mxd文件,而是托管的内存?希望大侠解释一下。接下文: