内存探寻1之——值类型和引用类型的内存分配机制

String对象和值类型的内存分配机制:    

      同样由前延伸,上上篇《由String类型分析,所产生的对参数传递之惑的解答》中,最后提及,如果将引用类型的按值传递和按引用传递,用托管堆表示,则更具说服力。在此附图说明:(如果印象模糊,可回看文章)

内存探寻1之——值类型和引用类型的内存分配机制 内存探寻1之——值类型和引用类型的内存分配机制

由上两图可以看到:

1.在图1(即上面图),当在按值传递引用类型参数时,接收参数的函数中(注意:依然在Stack上),依然存在一份拷贝——同样指向原始字符串("This is a String")的变量—funStr 。而此时亦即str和funStr引用都指向同一字符串。然而当函数中,对funStr重赋值("This is a changed String")时,即改变了funStr的指向!但此时 原函数中的str变量的指向并未改变(如图示),这样才产生代码演示的结果——没有改变其值;

2.在图2(即下面图),当按引用传递引用类型的参数时,其实质是 :告知编译器传递参数的地址,这样在函数体中对变量的赋值,实质上是通过原参数(str) 的指针,来改变原变量的值。在这里表现为改变原变量的引用方向(如图示),而对于以前的变量("This is a String")对象,由于在托管堆上,故当没有引用时,GC(垃圾回收器)会自动清理掉。

 

由上,已经可以看到String对象的内存分配模式,下面简要分析一下值类型的内存分配原理:

     由于值类型属于变量和值的共同所有体,即在Stack上,某一段内存地址就代表着变量(ex,int i),而同时它也蕴含着它的值(ex,3)。示例图为:

内存探寻1之——值类型和引用类型的内存分配机制

这个例子虽然简单,但却可以普世的代表着值类型的通用的内存分配。

  

自定义对象的内存分配机制: 

   前述对特殊对象类型String的内存分配做了概述,然而对自定义的对象呢?我们可以更深一步考虑:

首先:CLR管理的内存分配机制 

    CLR管理的内存,主要分为三块,依次为:

1.线程的堆栈:用于分配值类型。它主要受操作系统管理,不受垃圾回收器(GC)管理。当值所属的函数结束时,变量会自动销毁;

2.GC堆:用于分配小对象实例(小于85000字节);

3.LOH(Large Object Heap)用于分配大对象(超过85000字节),一般性了解;

其次:自定义对象的内存分配机制 

众所周知,我们创建对象的引用在Stack上,而实例则存在于Managed Heap上。然而我们需要通过下列几个概念,进而将Managed Heap做进一步的划分。先看在对象创建时,在对象中自动添加的附加成员:

1.TypeHandler,类型句柄:指向对应实例的方法表,其占用4个字节;

2:SyncBlockIndex,用于线程同步。它指向一块被称为同步块的内存块,管理内存同步,其同样占用4个字节; 

3.NextObjPtr:是托管堆所维护的一个指向下一个对象实例化时的起始地址的指针;

 

有了上述概念,我们把托管堆(Managed Heap)划分为GC堆(垃圾回收堆)Loader Heap(加载堆)。其中GC堆,不用多讲,即传统意义上的、用于存放对象实例的区域;而Loader Heap主要用于存放每个类所具有的方法表 (Method Table)(包括该类所包含的类类型、实现接口、静态字段,以及方法等)。其中Loader Heap不受GC控制(既不受某对象影响,显而易见,呵呵~)。

 

有了上述铺垫,我们大致可以讲自定义对象的过程归纳为:

1.构造实例化对象中TypeHandler所指向的对象(可认为是Method Table),包括实现接口、静态字段、方法等,并提交至Loader Heap上; 

2. 初始化实例的2个附加成员(TypeHandler和SyncBlockIndex),并且将TypeHandler指针指向Method Table;

 3.初始化构造器,对对象字段初始化;

 

下面通过建立一个类,并实例化对象,观察其在内存上具体分配,即更加清晰,看代码:

内存探寻1之——值类型和引用类型的内存分配机制

//Description: 通过建立类Student,演示对象的内存分配机制

//CopyRight: http://www.cnblogs.com/yangmingming

//Notes: 为简便,将类的建立,和实例化类放于一起

public class Student
{
    private string studentName;
    public string StudentName
    {
        get { return studentName; }
        set { studentName = value; }
    }
    private string studentClass;
    public string StudentClass
    {
        get { return studentClass; }
        set { studentClass = value; }
    }
    private int studentAge;
    public int StudentAge
    {
        get { return studentAge; }
        set { studentAge = value; }
    }

    public void ShowStudentInfo()
    {
        Console.Write("The Student name is {0} ,class is {1},age is {3}", studentName, studentClass, studentAge);
    }

    //实例化对象为:Student s=new Student();
}

内存探寻1之——值类型和引用类型的内存分配机制

此时对应的内存演示图为:

内存探寻1之——值类型和引用类型的内存分配机制

说明:

1.通过图示,可以将对象实例化时的内存分配充分展示;

2.方法表中,包含类中方法ShowStudentInfo、Student等,且还可以包括静态字段等(本例未列出);

3.方法表是依赖于类而存在的,先于对象而存在,这个顺续很重要; 

 

附:(嵌套类型的说明)

 1.当值类型中嵌套引用类型:此时其中的引用变量依然建立在Stack上,而其实例化对象依然建立在GC Heap上;

2.当引用类型中嵌套值类型:此时值类型在GC Heap上建立,即不在Stack上建立; 

 

由上论述,我们基本可以窥见值类型和引用类型的内存分配机制,同时也对在对象调用方法的原理上(通过TypeHandler),有了更多了解;

 

下一节讲述:从内存的角度,看继承和多态。未完待续。。。