内存探寻1之——值类型和引用类型的内存分配机制
String对象和值类型的内存分配机制:
同样由前延伸,上上篇《由String类型分析,所产生的对参数传递之惑的解答》中,最后提及,如果将引用类型的按值传递和按引用传递,用托管堆表示,则更具说服力。在此附图说明:(如果印象模糊,可回看文章)
由上两图可以看到:
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)。示例图为:
这个例子虽然简单,但却可以普世的代表着值类型的通用的内存分配。
自定义对象的内存分配机制:
前述对特殊对象类型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.初始化构造器,对对象字段初始化;
下面通过建立一个类,并实例化对象,观察其在内存上具体分配,即更加清晰,看代码:
//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.通过图示,可以将对象实例化时的内存分配充分展示;
2.方法表中,包含类中方法ShowStudentInfo、Student等,且还可以包括静态字段等(本例未列出);
3.方法表是依赖于类而存在的,先于对象而存在,这个顺续很重要;
附:(嵌套类型的说明)
1.当值类型中嵌套引用类型:此时其中的引用变量依然建立在Stack上,而其实例化对象依然建立在GC Heap上;
2.当引用类型中嵌套值类型:此时值类型在GC Heap上建立,即不在Stack上建立;
由上论述,我们基本可以窥见值类型和引用类型的内存分配机制,同时也对在对象调用方法的原理上(通过TypeHandler),有了更多了解;
下一节讲述:从内存的角度,看继承和多态。未完待续。。。