本博客主要讲述运行时类型对象线程栈和托管堆之间的相互关系静态方法实例方法和虚方法的区别以及内存的分配和回收
线程栈在一个进程中可能包含多个线程一个线程在创建的时候会分配到一个大小MB大小的栈栈用于存储方法的实参形参以及方法内部的局部变量栈是从高位内存地址向地位地址构建的由于栈有先进后出的特点所以先定义的变量后被回收
下面来看一个简单的例子让你更了解线程栈
由于线程栈是从高位开始分配内存先分配的我就画在上面了在调用F()方法时分配内存的顺序是name>n>F的返回地址>Age>name回收内存的顺序当然是反过来的在一个方法中应该包含一些序幕代码进行一些初始化工作还有一些尾声代码等方法执行完成之后做一些回收工作由于方法的返回地址先分配在方法执行完成的时候回到返回地址递归太深就容易出现栈溢出请看我的《递归再一次让哥震惊了》因为参数局部变量都必须等到方法返回的时候才能回收
在介绍托管堆之前先看看两个简单的类
publicclassPerson
{
privateintheight;
publicvoidSetHeight(intheight)
{
thisheight=height;
}
publicvirtualvoidSay(stringword){}
publicstaticstringHead()
{
returnmyhead;
}
publicstaticintAge=;
}
publicclassStudent:Person
{
publicoverridevoidSay(stringword)
{
ConsoleWriteLine(word);
}
}
staticvoidMain(string[]args)
{
Personstudent=newStudent();
studentSay(Hellocth);
studentSetHeight();
PersonHead();
ConsoleReadLine();
}
CLR会在第一次访问一个对象时加载该对象在这里定义变量student时会为Person对象在线程栈中分配内存第一次加载吗在构造一个Student对象之前先要加载Student对象并为Student类型对象分配内存并构建一个Student对象对象的地址存入线程栈中的局部变量student 中我们知道类型对象的内容包含类型对象指针同步索引块静态字段和方法(静态的和非静态的)不管是类型对象还是实例类型都必须有类型对象指针同步索引块我们知道静态字段属于类被这个类的所有实例共享当然静态字段的内存是在类型本身中分配的方法也是类的所有实例共享的他的内存也是在类型本身中分配的在每一个类型对象中都有一个方法表类中定义的方法都有一个对应的项
在构造一个对象的实例时只需要为类型对象指针同步索引块该对象的实例字段分配内存对于对象实例来说类型对象指针可以让实例访问类型对象中德静态字段方法等
Student是线程栈中的定义的一个局部变量保存Student的一个实例的在托管堆中的地址所以他可以访问Student对象中的字段方法其实访问方法是通过类型对象指针访问类型对象Student中的方法表中对象的项
Say方法的执行过程变量student指向的是一个Student对象调用的当然是Student类型对象中的Say方法尽管在定义student的时候是Person类型因为他是引用类型他指向的是托管堆中Student对象的内存然后遍历该对象的方法表找到该方法调用
特别说明虚方法JIT在虚方法中加了一些额外的代码方法每次调用的时候都会执行这些代码这些代码会检查发出调用的变量然后根据这个变量找到其应用的对象然后调用这个对象的方法若没有这些代码你觉得CLR是调用父类的方法还是调用之类的方法呢虚方法带来方便的同时也多了这些必须的检查的代码
SetHeight方法的执行过程和Say方法前面是一样只是在遍历Student对象的方法表时没有找到该方法我们知道父类中定义的非private方法都可以被子类继承是因为每个类型都定义了一个字段引用了他的基类如果一个类调用的方法那个方法不是自己定义的那么编译器会回溯类层次结构一直到基类Object找到相关的方法并调用如果没有找到相关的方法就报了异常呗所以SetHeight方法其实调用的是Person中的SetHeight方法
Head方法的执行由于Head方法是静态方法和上面两个方法有所不同调用静态方法的时候CLR会定位与静态方法对象的类型对象然后在对应实例对象对象的方法表中查找相关的记录项如果没有找到同样会回溯