zoukankan      html  css  js  c++  java
  • 同名笔记《你必须知道的.net》(一)

    博文带着3个疑问学习:(整理的有错误,请大家帮我改正)

    问题1:CLR管理内存的三块区域是什么?

    问题2:哪些操作会 创建对象和分配内存?

    问题3:内存的分配机制?

    1.CLR管理内存的三块区域
    注:内存——堆栈   堆(托管堆)

    线程的堆栈:用于分配值类型的实例-有操作系统管理分配释放内存。
    GC堆(托管堆):用于分配引用类型的实例对象内存小于8500 byte的。当有内存分配时,垃圾回收器"可能"会对GC堆进行压缩。
    LOH堆(Large Object Heap):用于分配引用类型的大对象实例(大于8500byte),不会被垃圾回收器压缩,而且只在GC堆完全被回收时回收。

    2.哪些操作会 创建对象和分配内存?
    IL指令:
    newobj:引用类型对象创建
    ldstr:string类型对象创建
    newarr:数组对象创建
    box:值类型转换引用类型,值类型字段拷贝到托管堆上发生 内存分配

    3.内存的分配机制
    3.1 堆栈的内存分配机制
    对于值类型:当作为类的值类型成员时,这个时候值类型将被分配在托管堆(堆)上。如:

    class Car
    {
    int carYear;
    string carName;
    }



    即:Car oneCar=new Car();  这个Car类的引用变量将被分配在线程堆栈上,而这个对象的成员,如:carYear,carName将被分配在堆上。
    注意:堆栈-stack  堆-heap

    对于堆栈的变量来说,是由操作系统来分配和释放内存的,操作系统维护着一个堆栈指针来指向一个自由空间(未被分配的内存的开始位)。堆栈的分配是从高位——>低位,而释放是从低位——>高位,如:

    static void Main()
    {
    int i=1;
    char a='A';

    }




    分析:
    分配
    第一步:C#程序从Main函数开始,每个线程堆栈都有一个初始化地址,比如这个程序的初始化地址是100;
    第二步:int类型占有4字节,那么开始堆栈指针是在100这个位置,然后分配4字节给值为1的Int类型(100-97)保存,然后堆栈指针指向96.
    第三步:char类型占有2字节,那么堆栈指针由96指向94。
    第三步:当运行到右括号的时候,将会释放内存
    释放
    第四步:释放的步骤是分配的反方向,堆栈指针逐步向上移动。

    注:上面的方式更可以说是“局部变量的分配机制”,因为这些值类型变量随着方法的结束而结束,效率高,但是内存容量小。
        
    3.2 堆(托管堆)的内存分配机制
    对于引用类型:引用类型的变量的内存是分配在堆栈上的,而引用类型的对象实例的内存是分配在托管堆上的。
    对于托管堆里有2个重要的区域:GC堆和加载堆(Loader Heap)

    GC堆存储对象实例字段,由GC管理。
    Loader堆有High-Frequency Heap、Low-Frequency Heap、Stub Heap,存储的是元数据相关的信息,比如:Type对象,即方法表。方法表创建于编译时,主要包含了类型的特征信息、实现的接口数目、方法表的slot数目等。 Loader堆不受GC控制,生命周期是从创建到AppDomain卸载。

    如下代码:
     

    class Program
    {
    static void Main(string[] args)
    {
    VIPUser vipuser;
    vipuser = new VIPUser();
    vipuser.isVip = true;
    Console.WriteLine(vipuser.IsVip());
    }
    }

    public class UserInfo
    {
    private Int32 age = -1;
    private char level = 'a';

    }
    public class User
    {
    private Int32 id;
    private UserInfo user;
    }
    public class VIPUser : User
    {
    public bool isVip;
    public bool IsVip()
    {
    return isVip;
    }
    }



    分析:
    第一步:Main函数开始,声明一个VIPUser类型变量,这只是一个引用,或者说是一个指针,它是存储在堆栈上的,占有4个字节。这个时候,没有指向对象,初始化为NULL。
    第二步:new对象,这个过程很复杂,因为IL代码为newobj,这个过程会一直向上查找其所有父类,直到Object类型,并且这个过程会计算类型和所有继承关系类型的字段,并且返回一个总的占有字节数。
    开始计算如下:
    VIPUser类的bool类型 (1字节)+User类的[Int32类型(4字节)+UserInfo类型的user的引用(4字节)]+UserInfo类的[Int32类型的(4字节)+char类型的(2字节)]=1+8+6=15字节。

    注意:在32位系统下,TypeHandle和SyncBlockIndex附加成员也会占有8字节。即:一共是23字节,但是托管堆上一般是按4字节的倍数分配的,所以会分配24字节的内存。

    注意:NextObjPtr是一个神奇的指针,他在托管堆中,标识下一个新建对象在托管中的位置,并且会返回对象实例的内存地址给 堆栈中的引用变量。
    第三步:分配内存
    对于堆栈是 先进后出,向低位扩展 ,分配内存是由上-下,释放内存是由下-上。
    对于堆是 先进先出,向高位扩展。分配内存是由下-上,由GC回收器回收内存。

    对于引用类型,父类在前子类在后,当发现内存不足时,会启动GC回收器,回收垃圾对象占用的内存。
    第四步:调用构造函数,进行初始化,完成创建对象过程
    如下:
    .构造VIPUser类型的Type对象,主要包括静态字段、方法表、实现的接口等,并将其分配在上文提到托管堆的Loader Heap上。
    .初始化vipuser的两个附加成员:TypeHandle和SyncBlockIndex。将TypeHandle指针指向Loader Heap上的MethodTable,CLR将根据TypeHandle来定位具体的Type;将SyncBlockIndex指针指向Synchronization Block的内存块,用于在多线程环境下对实例对象的同步操作。
    .调用VIPUser的构造器,进行实例字段的初始化。实例初始化时,会首先向上递归执行父类初始化,直到完成System.Object类型的初始化,然后再返回执行子类的初始化,直到执行VIPUser类为止。以本例而言,初始化过程为首先执行System.Object类,再执行User类,最后才是VIPUser类。最终,newobj分配的托管堆的内存地址,被传递给VIPUser的this参数,并将其引用传给栈上声明的vipuser。

  • 相关阅读:
    java回顾之多线程
    java回顾之异常
    模拟斗地主和冒泡排序
    java回顾之Map
    java回顾之集合List
    java回顾之树
    java回顾之单列集合、泛型、数据结构
    java回顾之类API再体验之引用类型小结
    java回顾之API初体验
    函数之 闭包函数 和 装饰器
  • 原文地址:https://www.cnblogs.com/IAmBetter/p/2340269.html
Copyright © 2011-2022 走看看