之前发过的那篇.NET的GC机制和GC知识学习里面有一选项为:
A. 值类型的对象会被分配到Stack上,而引用类型的对象会被分配到Large Object Heap上。
今天在写前一篇Blog时候,发现这句话其实大错特错,虽然这个选项本身就是故意加入了错误,作为选择题答案出现,但此错非彼错。还好题目没有正式使用,不然会被人打脸打得啪啪响。在尚未被人打脸之前还是先自打比较好。先看前半句:
值类型的对象会被分配到Stack上
拿值类型中最常被提到用来和引用类型相比较的Struct来说,它一定分配到Stack上吗?当然不是。这一点题目已经涉及到了,就是大于85k的对象会分配到LOH上。但忽略的一点是一般尺寸的值类型对象也有可能分配到Heap上:
- 类成员变量,就算是值类型也是在Heap上分配;
- 局部值类型变量,涉及到和闭包相关的特性时。
第一条就不说了。第二条例如下面这个例子,i和j都是局部值类型变量,他们都在Stack上吗?
private void Form1_Load( object sender, EventArgs e ) {
int j = 0;
int i = 0;
Func foo = () => {
return i;
};
}
当然是否定的,拿出il代码看一下就明了:从声明上来看,j的确是在Stack上没错,但i却是依附在一个编译器自动生成的类<>c__DisplayClass3上面;进行赋值时候i用的是stfld而不是stloc。这也就是说对于闭包涉及到的局部变量而言,CLR会将其依附到一个自动生成的类上进行存取操作,那么它自然就不分配到Stack上了。
.method private hidebysig instance void Form1_Load(object sender,
class [mscorlib]System.EventArgs e) cil managed
{
// 代码大小 31 (0x1f)
.maxstack 3
.locals init ([0] int32 j,
[1] class [System.Core]System.Func`1 foo,
[2] class WindowsFormsApplication1.Form1/'<>c__DisplayClass3' 'CS$<>8__locals4')
IL_0000: newobj instance void WindowsFormsApplication1.Form1/'<>c__DisplayClass3'::.ctor()
IL_0005: stloc.2
IL_0006: nop
IL_0007: ldc.i4.0
IL_0008: stloc.0
IL_0009: ldloc.2
IL_000a: ldc.i4.0
IL_000b: stfld int32 WindowsFormsApplication1.Form1/'<>c__DisplayClass3'::i
IL_0010: ldloc.2
IL_0011: ldftn instance int32 WindowsFormsApplication1.Form1/'<>c__DisplayClass3'::'
b__2'()
IL_0017: newobj instance void class [System.Core]System.Func`1::.ctor(object,
native int)
IL_001c: stloc.1
IL_001d: nop
IL_001e: ret
} // end of method Form1::Form1_Load
另外漏了一点,在更正:值类型并不总是分配在栈上一文中提到,还有一种会被分配到Heap上的值类型:
05 12月 2011 in 程序
Comments [1]
继函数形参传值传指针、GC机制的Blog后,貌似这又是说烂的问题?没办法,最近在回顾各种知识,发现有联系的就拿出来写一写,省得日后发霉。如果你现在对闭包还不是很了解,可以阅读一下学习Javascript闭包(Closure)、深入理解JavaScript闭包(1)和理解 JavaScript 闭包三篇文章。难度依次上升。
JavaScript的闭包
闭包的概念已经比科学发展观都深入人心了,简单说它就是“具有独立作用域并且可以引用外层作用域的对象”。例如C#中的匿名函数和Lambda表达式就是基于的闭包概念,其他的高级语言,类似Java、JavaScript和Python等也都有闭包的应用。当然也不要忘了JavaScript的内存泄露主因之一也是闭包,所以先看JS好了。
还是先从代码入手,下面两段代码是从阮一峰老师的那篇闭包讲解中摘取的最后习题,那么现在提问,两段代码分别alert什么内容?
var bar = "Global";
var object1 = {
bar : "local",
foo : function(){
return function(){
return this.bar;
};
}
};
alert(object1.foo()());
//我是分隔线
var object2 = {
bar : "local",
foo : function(){
var that = this;
return function(){
return that.bar;
};
}
};
alert(object2.foo()());
Read more >>>
05 12月 2011 in 程序
一些关于闭包的废话已关闭评论
在刚刚完成的一套.net笔试题卷子中,出了这样一道题:
面有关垃圾收集(GC)机制的说法中正确的是:
- A. 值类型的对象会被分配到Stack上,而引用类型的对象会被分配到Large Object Heap上。
- B. 使用try/catch/finally在效率上并不优于使用using的方式。
- C. 可以实现自定义引用类型的Finalize方法,但它不能被重载。
- D. GC的垃圾回收动作可以在代码中手动调用。
答案是BCD,A中错误在于引用对象会被默认分配到GC Heap的SOH(Small Object Heap)上(update:错误不仅如此,请参考打自己脸——有关.net对象分配),超过85KB对象会被分配到LOH。至于85K这个Magic Number的由来,应该是编写.Net框架的工程师们经过平时各种使用场景和优化推算出来的,我们不必多费心研究。
那么GC Heap为什么要分两类呢?这要从SOH和LOH不同的垃圾清理机制说起。
.Net的GC机制
GC Heap
对于LOH来说,清理垃圾的过程等于“回收+归并”,即不仅要清理掉内存空间,还要将尚存的对象向着一个方向移动直到和之前的对象紧紧贴在一起[1][3]。如下所示,SOH上有四个对象:
[obj1][ obj2 ][ obj3 ][obj4]
↑
0代指针(p0)
经过一次清理后,obj2和obj4没了,留下来的都是1代对象,并将0代指针移动到内存低位的第一个空闲空间处:
[obj1][ obj3 ][obj5]
↑ ↑
p1 p0
2代对象的生成类似,这样的移动保证不会产生大量的内存碎片,最大程度提高了内存使用效率。
可惜对于大对象来说,移动的成本过高,所以会优先采用查找可用剩余碎片空间的方式,将可以容纳新对象的空间重复利用。类似上面的内存情况在0代清理、新分配了obj5后会变成:
[obj1][obj5] [ obj3 ]
obj5和obj3之间的内存如果小于85K就很可惜,因为再也利用不起来了。
不过需要注意的是,虽然大对象目前不会被归并处理,但不保证未来的CLR版本会不会实现一个LOH也进行存留对象移动的版本,所以希望保持现在这种行为的话,需要在变量前加fixed关键字进行标识。
分代处理
上文提到的0、1代是.Net中采用的分代垃圾回收机制的体现。目前.Net的GC中支持的代数为2(GC.MaxGeneration),一般来说,可以将0-2这三代对象视作:
- 0代,大多数对象,GC进行回收时候都会被处理掉
- 2代,少数常驻内存的对象,例如asp.net和整个网站生存期等同的一些全局对象
- 1代,介于0代和2代之间的一个灰色地带。
在GC动作的时候,0代会首先会被回收,如果需要的内存空间不够用再依次处理1和2代对象。
对于程序中的引用对象,可以通过GC.GetGeneration()获取它的代数,这个方法还有一个重载版本支持WeakReference。
Read more >>>
01 12月 2011 in 程序
Comments [2]
这次的冬季书市感觉比春季和去年几次的规模都大,而且最大特点就是卖东西的摊位类别更杂。之前觉得看到卖茶叶就是极限,今天去竟然还有北京原来有名的义利巧克力在促销。
进门之后第一印象,还是人多。虽然我不喜欢凑热闹,买书都找安静地方,但每次乱哄哄的书市却并不让人烦。而且依然是男女老少各个年龄都有、背包手提小拖车齐上阵,甚至还见到了超市那种推车,明显是朝着一折的《二十四史》这类书籍去的。
Read more >>>
27 11月 2011 in 读书
地坛冬季书市归来已关闭评论