揭开CLR神秘面纱-----第二步 垃圾回收之(制造垃圾)

 

阅读本章前,要求读者对值类型与引用类型有相当的了解

关于值类型与引用类型的深入讲解,请参见ANYTAO的[你必须知道的.NET] 第八回:品味类型---值类型与引用类型(上)-内存有理  

----.NET应用程序初始化的时候所有的引用类型都会被分配到托管堆上面。而只有托管堆里面的资源才能被垃圾回收机制给管理起来。所以,我们要明白,CLR的垃圾回收机制并不是针对所有数据的。我们该手动释放的时候必须得要手动释放。

   我们从何得知这一结论?如何论证这一点?

   程序是如何在CLR中运行的呢?那我们CLR中垃圾回收机制又是怎么样运行的呢?我们是否需要再进一步去研究呢?我们如果不手动释放值类型,它们会常驻我们宝贵的内存空间吗?我不得不承认,这里牵扯到了很复杂的算法和操作系统及Win32编程的相关知识。这可能需要大篇幅的文字来说明这些问题。

   既然有垃圾回收,肯定就有垃圾制造者在制造垃圾。这些垃圾是如何被制造出来的,就是我们今天试图弄清楚的问题。希望能通过以下文字描述清楚,程序运行时各阶段的内存使用状态。

   请看下面的代码:

很明显,上面定义了一个类,一个枚举,一个结构,这是一段表面看似很普通的代码,但其实骨子里却大有乾坤。我们再次打开ILdasm来看下它的真面目。


     可以看的出,两个类都有.ctor,就是说,两个类都有构造函数。点开会发现原来是继承自Object的构造函数。这也就解释了为什么我们定义类的时候就算不显示声明构造函数,系统也会自动给你声明构造函数。功劳来自于CLR.

    细心的朋友又会发现另外一个问题,那这里这个结构明明没有看见构造函数,那它为什么也能new呢?关于这里,可以参考anytao关于new的文章

我这里谈谈自己的看法。我们点开Main函数。会看到如下内容

     

   可以看到,在init的时候,里面传入了11个参数。分别就是我们代码里使用的11个对象。Init指令起的作用是“init specifies that the variables must be initialized to the default values for their respective types” init 指定的变量必须被各自类型的默认值初始化--原文地址

    继续我们上面的问题,结构没有构造函数为什么也能new呢?分析IL代码,发现new class的时候也就是new 引用类型的时候,IL的指令是newobj,并且还instance了相应的构造函数,new struct的时候,IL的指令却是initobj。这似乎就是问题的根源所在。
  先说说newobj指令吧,执行该指令的时候,他会将我们指定的类型实例化,而且根据需要会将新类型中的成员初始化一次,再把这个对象送到托管堆上。然后再根据参数调用构造函数。构造函数对该类型做的操作就是在托管堆中进行了,最后在线程堆上只是把这个对象的引用压栈。(我们的托管堆上其实存在一个指针。该指针就是我们垃圾回收的最高检察官。关于他,咱们以后再做描述。)

    那我们的initobj做了什么工作呢?MSDN上给出的解释很简单。该指令就是初始化值类型。因为编译后,非primitive type的值类型已经存在于线程堆栈上。但是还没有初始化。所以需要做这一步操作。而所有的primitive type在编译时会初始为0.这也就是为什么char和MyStruct同为值类型而char没有init的原因了。    

    通过上面的描述,我们应该可以清晰的看出.NET CLR中的运行过程,对象的创建是一个递归的过程。恰恰也是咱们通常所说的Builer模式的一个体现。至此,我们的“垃圾”都已经制造完毕,下一步可以讨论生成的垃圾该如何回收了。这一步最重要的理解在于初始化。
                                                        MSIL速查手册下载,感谢无私奉献

版权声明:原创技术文章,如需转载,请声明出处。不得用于任何商业形式活动,否则将追究法律责任。

PS:非常感谢sumtec@beijing,剑在上海^^,两位网友提出的意见。

 

posted on 2007-07-17 20:27 刘荣华 阅读(2096) 评论(23)  编辑 收藏 所属分类: DotNet随笔

评论

#1楼  2007-07-17 20:37 Anytao      

继续努力,形成系列,成为CLR团队的精品推荐系列,加油。不过组织上能够更加系统化就更加完美了。   回复  引用  查看    

#2楼 [楼主] 2007-07-17 20:40 刘荣华      

@Anytao
呵呵,谢谢。争取吧,工作毕竟才是第一位的:)
  回复  引用  查看    

#3楼  2007-07-17 22:32 chinabin [未注册用户]

newobj同intiobj的区别是除了两者都有进行初始化之外,后者没有调用构造函数,您讲了初始化的两个步骤做了什么事情,但是您取没有讲调用构造函数又做了什么?如果您能把这个也讲讲,我认为这篇文章您就写得比较透彻,也达到了读者看这篇文章的一个初衷!谢谢!!注:如果讲穿了可能很简单,但还是希望你把一个问题讲明白。再次罗嗦!   回复  引用    

#4楼  2007-07-18 00:35 维生素C.NET      

CLR对于Large Object是有特殊照顾,GC当然肯定也是如此的,从LO诞生到对其操作和对其销毁都与其他类型不同,如果要讲GC(尤其是.net上的generation模式的GC)我觉得LO是不能缺少需要讲解的部分   回复  引用  查看    

#5楼  2007-07-18 01:49 Artech      

@维生素C.NET
不错,在managed heap中,又分很多的区域,不同的对象分配在不同的区域。比如基于Type的Metadata信息、Instance对象,LO分别分派在Loader heap, GC heap,LO heap。 GC只能对GC Heap的对象进行Garbage collection。

@刘荣华
Garbage collection的内容很多,深究下去会很有意思。希望再接再厉,越来越好!   回复  引用  查看    

#6楼  2007-07-18 08:51 剑在上海^^ [未注册用户]

initobj指令是不分配内存的,只是初始化值类型数据
比如
int i=0;
int i=new int();
两者是一样的   回复  引用    

#7楼  2007-07-18 08:54 Zealic      

Primitive type 翻译成 元型或元类型   回复  引用  查看    

#8楼  2007-07-18 08:59 维生素C.NET      

@Artech
yep, 就像说到GC的性能问题时大多数情况下是在G1到G2的操作和LO的移动上   回复  引用  查看    

#9楼  2007-07-18 09:14 Artech      

@维生素C.NET
是呀,Gereration的提升往往是造成内存压力的一个很大的因素。   回复  引用  查看    

#10楼  2007-07-18 09:16 Artech      

@Zealic
觉得翻译成基元类型更好:)   回复  引用  查看    

#11楼  2007-07-18 09:29 刘荣华      

@维生素C.NET
@Artech
谢谢二位,说的非常好。写这一步的时候,思考了许久。正如我在日志中说的,这里面算法会非常复杂,牵扯到的知识面也非常之深入和广泛。掐指估算了一下发现时间并不允许我花太多精力来构思。从GC谈起的初衷是想从对象的实例化逐个深入到事件,委托,反射,等等内容。想想其实量还是蛮大的。有点想打退堂鼓的意思。呵呵
但承蒙抬爱,还是尽力写出通俗易懂且较为深入的日志。
希望二位能多多指教。

@剑在上海^^
嗯,int i = 0和int i = new int()是一样的。

但是,是会分配内存的。借用MS的一句话“将位于指定地址的对象的所有字段初始化为空引用或适当的基元类型的 0”这里的基元类型就是我上面说的primitive type呵呵。
谢谢关注。

@Zealic
谢谢
@Artech
MS给出的是基元类型,但是我觉得我理解起来有点问题。所以就没给出官方标准答案。:)
  回复  引用  查看    

#12楼  2007-07-18 10:27 周银辉      

good   回复  引用  查看    

#13楼  2007-07-18 10:48 小小 [未注册用户]

原来是这样的啊。
老大,我崇拜你。非常感谢。
不过语言表达要是再精炼些,语句结构再衔接的系统点就是太完美了。

  回复  引用    

#14楼  2007-07-18 11:36 刘荣华      

@周银辉
:)
@小小
过奖,过奖,谢谢   回复  引用  查看    

#15楼  2007-07-18 17:11 sumtec@beijing      

init 应该不是一个函数,而是.local 的一个参数或者描述。
IL里面以“.”开头的都是伪代码,是一种描述,给编译器看的。
你说的init完整的应该是:
.local init (....)
大概意思就是下面有若干个本地变量,分别为。。。

另外,你说的分配空间,也不完全准确。
实际上每个本地变量所占用的空间(其实全局变量也一样),在编译时已经决定了。对于int就是4个字节,对于某些结构类型,那就是该结构的大小,对于ByRef的类型,就由.NET的版本决定,可能是4字节,也可能是8字节(32位和64位)。
至于说newobj和initobj,除了你说的差别,还有一个差别就是,对于ByRef用前者,ByValue用后者。因为ByRef,也就是我们平常说的引用类型对象,那4个字节或者8个字节保存的就是这个对象内容所在的地址。newobj在这里确实是要负责在堆里面分配内存空间的,但是跟刚才说的4个字节或者8个字节没有关系,那些是在栈里面的,在进入这个函数体的时候就分配好了。

全局变量和本地变量也有点类似,当然也有点差异,不过没有追究的必要了。到这里已经挖的够深的了,对于我这样的种萝卜的农民来说,没有啥意义。   回复  引用  查看    

#16楼  2007-07-18 17:51 U2USoft      

GC已经是老生常谈的话题了,已经谈烂了。大家还是多花时间研究算法吧。。。   回复  引用  查看    

#17楼  2007-07-18 18:01 刘荣华      

@sumtec@beijing
非常感谢提出意见,理不辩不明啊。
init指令的确切目的,还在研究当中。可能就是您说的这意思。

这个分配空间,是在内存上开辟空间的意思。这些变量不可能会存在于CPU的寄存器中的。

关于您补充的那个差别,我认为我已经写清楚了哦:),开篇的时候,我就说明要让读者对reference type 和value type有一个清醒的认识。呵呵。所以前提是大家能清楚的知道我的Demo里值类型和引用类型。
非常感谢指正,希望能多交流。
  回复  引用  查看    

#18楼  2007-07-19 07:14 剑在上海^^ [未注册用户]

@刘荣华
int i;//声明变量时分配内存
i=new int(); //初始数据,调构造函数(非initobj调用)
也就是说当new int()时是不另外分配内存的,如果new int()也分配了内存,那堆栈上岂不是有2个相同的数据区?怎样想都是不通的
"将位于指定地址的对象的所有字段初始化为空引用或适当的基元类型的0"并没有说有没有分配内存,这段话的意思大致是把相应值类型对象(比如结构)里的引用类型字段赋NULL,值类型字段赋值相应的系统默认值
基元类型包括sbyte(8)、byte(8)、short(16)、ushort(16)、int(32)、uint(32)、long(64)、ulong(64)、char(16)、float(32)、double(64)、bool(True or False)、decimal(128)、object(基类型)、string(字符数组)

再看引用类型
myclass mc;//声明变量时分配内存
mc=new myclass();//分配内存空间,初始数据,调构造函数,返回空间地址
所以当mc=new myclass();时,mc得到了空间的地址,说明new myclass()是另外分配内存的,
其实这也是newobj和initobj的区别之一

引用MSDN一句话"Initobj用于初始化值类型,而newobj用于分配和初始化对象。" 注意 分配 2个字

感谢LZ能回复我的贴:)   回复  引用    

#19楼 [楼主] 2007-07-19 10:50 刘荣华      

@剑在上海^^
非常感谢。
关于initobj的一些措辞,已经修正。我会继续写下去,请多提意见。
  回复  引用  查看    

#20楼  2007-07-19 12:54 test [未注册用户]

GC已经是老生常谈的话题了,已经谈烂了。

========================================

同感   回复  引用    

#21楼  2007-07-19 14:20 刘荣华      

@test
:)
能否请您也谈点没有被谈烂的话题给我们学习学习呢?
似乎我这里还没具体到真正的回收噢。。
呵呵。
感谢回复。   回复  引用  查看    

#22楼  2007-07-19 14:47 guest [未注册用户]

BS楼上那些说风凉话的
好像自己有多牛一样。
我觉得LZ写的很好,对我这样的在校学生来说很有帮助。
我想请问下,如果我的引用类型里要使用值类型的话是怎么处理的呢?   回复  引用    

#23楼  2007-07-19 15:15 刘荣华      

@guest
:)谢谢。
共同学习。
引用类型里使用值类型的话,就要进行装箱,意思就是会把值类型转化成引用类型。
通常的步骤是,把引用类型复制到托管堆上,然后再返回该对象的地址。   回复  引用  查看    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-07-19 10:49 编辑过


相关链接:
 
<2007年7月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

导航

统计

公告

当前时间

与我联系

搜索

 

常用链接

留言簿(2)

我参与的团队

随笔分类(22)

文章分类(1)

收藏夹

网站链接

最新评论

阅读排行榜

评论排行榜