happyhippy

这个世界的问题在于聪明人充满疑惑,而傻子们坚信不疑。--罗素
posts - 118,comments - 215,trackbacks - 45


1. 值类型实例的创建位置:
    对于值类型的实例,CLR在运行时有两种分配方式:(1) 如果该值类型的实例作为类型中的方法(Method)中的局部变量,则该实例被创建在线程栈上;(2) 如果该值类型的实例作为类型的成员,则该实例作为引用类型(引用类型在GC堆或者LOH上创建)的实例的一部分,被创建在GC堆上。下面这段代码演示了这两种情况:

public class Test1
{
    
private int i;//上面(2)中的情况,生成Test的实例的同时,int类型的实例i被创建在GC堆上
    public Test1()
    
{
        
byte b =0;//(1)中的情况,byte类型的实例b被创建在执行这段代码的线程栈上
    }

}

 


2. 值类型实例的创建位置:
    对于引用类型的实例,CLR在运行时也有两种分配方式:(1) 如果该引用类型的实例的Size<85000Byte,则该实例被创建在GC(Garbage Collection)堆(当CLR在分配和回收对象时,GC可能会对GC堆进行压缩);(2) 如果该引用类型的实例的Size>=85000byte,则该实例被创建在LOH(Large Object Heap)上(LOH不会被压缩)。面这段代码演示了这两种情况:

public class Test2
{
    
private int[] intArr;
    
public Test2()
    
{
        
private Object o = new Object();//引用o存在线程栈上,它指向GC堆上的Object实例
        intArr = new int[21250];//符合(2)中的Size条件,int数组的实例被创建在LOH上
    }

}

 


3. 托管对象被引用的七种途径:
    上面的代码片段Test2中,也演示了引用托管对象的两种途径:
(1) intArr随Test2实例创建的同时,被创建在GC堆上,由GC堆上的类型(Test2)实例持有托管对象(int数组的实例)的引用;
(2) 引用类型的变量o存在线程栈上,由线程栈上的局部变量(o)持有托管对象(Object实例)的引用。
其实还有以下五种途径可以持有托管对象的引用:
(3) LOH堆上的实例(原理同1);
(4) 在与非托管语句交互操作或者P/Invoke情况下的句柄表(Handle Table);
(5) 寄存器,例如执行实例方法时的this指针和方法参数(IL中的call、callvirl指令是将函数参数进行自右往左的压栈处理通过栈实现传递参数;而fastcall指令则可以最多将两个参数分别保存到ECX和EDX两个寄存器中,通过寄存器来实现参数传递,以提高程序的性能;这里的方法参数是指后一种fastcall的情况);
(6) 拥有终结器(finalizer)方法的对象的终结器队列
(7) 所属类型的HandleTable(对象创建时,该HandleTable将持有一个弱引用Weak Reference)。下图演示了这几种情况:

 

 

4. 托管对象的结构:
    从上图中,我们可以看到,托管对象的引用并不是指向对象的起始位置,而是相对起始位置有+4Byte(DWord)的偏移量,这4个Byte称为对象头。下面转载一段对该对象头的介绍:对象头保存一个间接指向SyncTableEntry表的索引(从1开始计数的syncblk编号)。SyncTableEntry维护一个反向的弱引用,以便CLR可以跟踪SyncBlock的所有权。弱引用让GC可以在没有其它强引用存在时回收对象。SyncTableEntry还保存了一个指向SyncBlock的指针,包含了很少需要被一个对象的所有实例使用的有用的信息。这些信息包括对象锁,哈希编码,任何转换层(thunking)数据和应用程序域的索引。对于大多数的对象实例,不会为实际的SyncBlock分配内存,而且syncblk编号为0。这一点在执行线程遇到如lock(obj)或者obj.GetHashCode的语句时会发生变化,如下所示:

1Object obj = new Object();
2lock(obj) /* Do some synchronized work here */ }
3obj.GetHashCode();

    在以上代码中,smallObj会使用0作为它的起始的syncblk编号。lock语句使得CLR创建一个syncblk入口并使用相应的数值更新对象头。因为C#的lock关键字会扩展为try-finally语句并使用Monitor类,一个用作同步的Monitor对象在syncblk上创建。堆GetHashCode的调用会使用对象的哈希编码增加syncblk。在SyncBlock中有其它的域,它们在COM交互操作和封送委托(marshaling delegates)到非托管代码时使用,不过这和典型的对象用处无关。

    紧跟在syncblk编号后的是一个TypeHandle句柄(占4个byte),有点像C++中的虚方法表指针,但实际上这个TypeHandle指向的MethodTable比C++中的虚方法表复杂得多(这里先不介绍,以后有机会再介绍)。

    在TypeHandle之后才开始存放实例字段的实例。默认情况下(即相当于在类上运用特性StructLayoutAttribute(LayoutKind.Auto)),实例字段会以内存最有效使用的方式排列,以便更有效地使用内存,排序之后字段在内存中的布局顺序跟字段在类中声明的顺序不一定相同,这样也给我们计算托管对象的Size带来了不便....


   <<未完待续>>下一篇将继续讨论如何获得托管对象的Size。 


happyhippy作者:Silent Void
出处:http://happyhippy.cnblogs.com/
转载须保留此声明,并注明在文章起始位置给出原文链接。
 
posted on 2007-04-12 06:07 Silent Void 阅读(1619) 评论(13)  编辑 收藏 网摘

FeedBack:
2007-04-13 15:11 | snowdoggie[未注册用户]
标题有错误
“托管对象被引用的7中途径”

  回复  引用    
#2楼[楼主]
2007-04-13 18:59 | Silent Void      
@snowdoggie
我表达能力不好,也不知道咋表达,就这样胡乱写上去了:)

  回复  引用  查看    
2007-04-14 23:20 | snowdoggie[未注册用户]
@Silent Void

hoho,我的意思是说 “七种途径”

  回复  引用    
#4楼[楼主]
2007-04-15 09:14 | Silent Void      
@snowdoggie

谢谢提醒,现在改过来了^_^

  回复  引用  查看    
2007-11-06 13:43 | 雾[未注册用户]
问一下,什么是线程栈吗?不是stack?
  回复  引用    
#6楼[楼主]
2007-11-07 15:57 | Silent Void      
@雾

操作系统为线程维护的栈,为了更程序中我们自己构造的栈加以区别,所以称其为线程栈.

  回复  引用  查看    
2008-04-01 15:01 | zicjin[未注册用户]
有问题请教:
"对于引用类型的实例,CLR在运行时也有两种分配方式:(1) 如果该引用类型的实例的Size<85000Byte,则该实例被创建在GC(Garbage Collection)堆上(当CLR在分配和回收对象时,GC可能会对GC堆进行压缩);(2) 如果该引用类型的实例的Size>=85000byte,则该实例被创建在LOH(Large Object Heap)上(LOH不会被压缩)。"

被压缩的话应该是执行效率变得更低,,占用内存能够降低吧,, 如果是这样的话,,我怎么觉得应该是去压缩>85000Byte的实例,,而不是压缩<85000Byte的实例呢?

  回复  引用    
#8楼[楼主]
2008-04-01 15:14 | Silent Void      
@ zicjin@gmail.com

1. 这里的“压缩”,是指GC移动堆上的对象,从而减少内存碎片,而不是对对象的内容本身进行压缩。
2. 压缩的好处是:
(1) 减少内存碎片;
(2) 压缩完成后,有个变量记录空闲内存的起始地址,下次在堆上创建对象时,可以直接用O(1)的复杂度来申请内存,而不用遍历GC堆。

  回复  引用  查看    
2008-12-09 10:01 | 梦涯      
为什么图片看不见了,我一直以为内存中就两块区域,一个堆栈,一个托管堆
看了LZ的文章虽然知道了还有LOH(Large Object Heap)这样的东西,但是我对内存本身就混淆了,虽然我本能的认为这个LOH(Large Object Heap)应该是属于托管堆的,LZ能不能说说堆栈和堆下面的分类~~期待

  回复  引用  查看    
#10楼[楼主]
2008-12-09 22:46 | Silent Void      
@梦涯
原来的图片丢了,现在已重新上传,谢谢提醒/:)
按我的个人理解,程序运行起来后,从程序的角度来看,内存被分成多个区域,包括:代码段、数据段、维持程序运行的线程栈等;如果有动态申请内存的话,就还会有堆(.net下面就是托管堆)。
LOH也是一种托管堆,其中的对象在使用完毕后,由GC进行回收,这点儿与普通的托管堆相同;但不同的是,LOH上的对象被回收后,其不会被压缩;有关垃圾回收和压缩,其中有较复杂的代龄算法,可以看看《.net框架程序设计》,里面有较详细的描述

  回复  引用  查看    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 710929




相关文章:

相关链接: