试着把.net的GC讲清楚(1)

GC(garbage collection)是对内存管理中回收已经不用的内存的一种机制,我们熟知的java和.net都有自己的GC机制,是内存管理的一部分。

为什么会有GC呢?是因为动态的内存分配和分布操作系统是不管的,得各类语言自己实现,例如c和c++自己需要手动管理分配的内存资源,如果不手动释放,那么会造成已经无用的内存不能被操作系统识别使用,也就是所谓的内存泄漏。

.net的GC都是发生在堆(heap),因为这个动态的内存是在堆上分配的。为什么.net 没有像c++一样提供手动管理内存的操作?因为手工管理内存非常容易出问题,开发人员不应花费时间在这个上面,避免人为问题,就找一个管理内存的“人”来,处理这些事情,于是GC就出现了(说一句话,以前自己是c、c++出身,真的一个语言影响一个人的知识广度和深度),大家都不用考虑这些事情,集中在重要的事情上,就像自己找了个管家,而且是专业的,不会出错的那种,省心。

GC有哪些分类?

我了解到的有:Reference Count、Mark and Sweep(升级版Mark and Compact)、Copy and Collection
现在java和.net 使用的是Mark and Compact算法,这个算法是从Mark and Sweep算法演变过来的,下来就讲讲Mark and Sweep。

Mark and Sweep:分为两个阶段,第一阶段标记所有现在还可以使用的对象,第二阶段清除标记的对象之外的内存。

在.net中,GC管理了一组root(由全局对象组成),通过遍历所有的root机器引用的子对象,进行内存中的存活对象的标记,之后就清除未标记为存活的对象。这就是Mark and Sweep算法,但是这个造成了一个问题,就是回收后的内存是成了筛子了,这个时候如果来一个大的对象需要分配内存,那么空余内存总额大于分配对象的大小,但是找不到一个连续的可以容下这个对象大小的内存,这个时候怎么办?其实模拟操作系统,再做一个内存管理的机制就行,在逻辑上看着连续就行了。当然这个不是本次讨论的对象,Mark and Compact解决了内存不连续的问题,因为它把内存做了一次整理(把不相邻的内存移动到一块,看着就连续了)

Mark and Compact:在Mark and Sweep基础上做了一次内存整理,因为内存做整理的时候,对象的引用是不能被使用的,引用地址会变,所以啊,GC的时候,使用到这些对象的线程什么的是会被挂起等待的,也不能经常回收内存,不然性能堪忧,就是因为回收导致挂起了。

啥是0代、1代、2代对象?

要解释这个问题,还得从内存回收时间说起,这里有个假设(其实也是规则)回收一个内存中所有对的时间大于内存中部分对象的时间,于是就把内存中对象分成了几代,0代对象指最新分配内存的对象,一次类推。其实多少代,这个由GC决定,.net中GC中代数是3代(这个值暂时不能确定能不能改)。

GC怎么管理代对象呢?一般情况下,分配的对象都是0代对象,在分配对象内存时,如果0代对象的内存已经不能容纳新对象了(超过0代对象内存的上限),在gc回收一次0代后,这个还存活的对象代数加1(GC.Collect();GC.GetGeneration(obj)代码验证过,现在不清楚自动触发是不是回收一次加1),同理如果1代对象超过了1代内存的上限,也会触发gc回收1代对象。那么这个内存回收是定是这样的么?不一定,毕竟微软提供了手动触发gc的功能,就是GC.Collect(),有兴趣可以翻翻这个方法。

代数的大小,查了很多资料之后,只发现一篇文章说到,.net中0代和1代之和为16MB,2代内存上限非常的大,具体有framework版本和其他一些因素决定的。

//验证回收一次,对象就升一代 Object obj=new Object(); Console.WriteLine(GC.GetGeneration(obj)); GC.Collect(); Console.WriteLine(GC.GetGeneration(obj)); GC.Collect(); Console.WriteLine(GC.GetGeneration(obj)); Finalize、Dispose是啥?如何理解?

.net中有托管资源和非托管资源的分类,托管资源.net自己就可以管理,非托管资源,需要特殊的方法,也就是托管资源在GC的时候,.net可以自己识别,但是非托管资源,GC是自动释放不了的。

什么是非托管资源?这让我想起之前用mfc写windows程序的时候,什么画刷、画笔、com之类的,就是非托管资源,还有数据库连接、文件、套接字之类的也是,哦,还有流之类的都是。

这些非托管资源,一般都需要自己释放资源,.net提供了IDsiposable的接口,实现这个接口的方法,在里面进行资源释放,使用using语句来简化这个非托管资源的释放工作。

Finalize:这个也能用来释放非托管资源,与IDsiposable接口区别是,它的调用时机是不定的,因为它是由GC调用的,GC调用真的不定的,因为调用一次GC调用会降低程序性能(前面说的,内存压缩导致引用需要变化,而因为线程挂起),下面来说说为什么它是由GC调用的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzywg.html