内存管理

 
 

栈(stack)
 
值类型存储在栈中
 
栈模拟图:
800000(已用)栈指针目前位置
799999(未用)
799998(未用)
799997(未用)
799996(未用)
799995(未用)
地址从高到低
 
工作原理:
{
    int a = 3;
    {
        double b = 320.00;
    }
}
int c = 4;
 
①a进入作用域,赋值为3,int型4个字节,分配799999 - 799996,此时栈指针下一个地址是799995
 
②b进入作用域,赋值为320.00,double类型8个字节,分配79995 - 79988,此时栈指针下一个地址是799987
 
③b出作用域,删除变量地址,栈指针下一个地址是799995
 
④a出作用域,删除变量地址,栈指针下一个地址是799999
 
⑤c进入作用域,赋值为4,int型4个字节,分配799999 - 799996,此时栈指针下一个地址是799995
 
由此可见:栈释放变量,其顺序总是与给他们分配内存的顺序相反。(先进后出)
 

托管堆(heap)
 
引用类型存储在托管堆上。
 
栈模拟图:
800000(已用)栈指针目前位置
799999(未用)
799998(未用)
799997(未用)
799996(未用)
799995(未用)
 
堆模拟图:
200005(未用)
200004(未用)
200003(未用)
200002(未用)
200001(未用)
200000(已用)
199999(未用)
199998(未用)
199997(已用)
地址从低到高
 
 
工作原理:
{
    Student m_student;
        m_student = new Student();
    {
        Student m_student2 = new GoodStudent();
    }
}
 
①Student m_student ==>声明Student类的引用m_student,因为是引用还未是对象,所以在栈上分配存储空间,m_student引用占4个字节,包含了存储Student引用的地址,分配799999 - 799996,此时栈指针下一个地址是799995
 
②m_student = new Student() ==>分配堆上内存,以存储Student对象实例,假设Student对象占64字节(CLR在托管堆上搜索能够放得下64字节的空间,比如199998 - 19999是空的但是不够放下64字节,所以往下找),在托管堆上分配 20001 - 20064位置
 
③Student m_student2 = new GoodStudent() ==>从栈上分配799995 - 79991给m_student2引用,从托管堆上分配20065 - 20128给m_student2对象。
 
④m_student2引用出作用域,释放m_student2引用,收回栈上799995 - 79991地址,但是m_student2对象还在托管堆的20065 - 20128地址上,除非程序终止或者垃圾回收器删除它,而且只有该数据不被任何变量引用时才可以删除。
 
⑤m_student引用出作用域,释放m_student引用,收回栈上799999 - 799996地址,但是m_student对象还在托管堆的20001 - 20064地址上,除非程序终止或者垃圾回收器删除它,而且只有该数据不被任何变量引用时才可以删除。
 

 
垃圾回收
 
垃圾回收机制运行时,它会删除不再引用的对象,删除后,堆会立刻把释放的内存与空闲的内存结合在一起。
效果图如下:
 
垃圾回收前:
已用
已用
已用
空闲
空闲
已用
垃圾回收后:
已用
空闲
空闲
空闲
空闲
已用
但是,有一个问题,如果新的引用需要不止4个字节的存储空间,假如32个字节,则需要跳过这个存储空间继续寻找下一个32个字节大小的空间。
所以垃圾回收器还做下一步工作,删除不在引用的对象后,将其他对象移动回堆的顶部,再次形成连续的内存块,那样堆就跟栈一样拥有连续的空闲的内存空间了(但是在此过程中需要调整这些引用的地址)。
总结:垃圾回收器,回收引用,修改引用地址,使堆内存成块。
 
垃圾回收期使用场景:一般CLR自动运行垃圾回收机制,不要认为的进行回收,回收原理如下图。使用场景之一:有大量的对象刚刚取消引用。
 
eg.
使用 Optimized 设置对第 2 代对象进行垃圾回收。
using System;
class Program
{
    static void Main(string[] args)
    {
        GC.Collect(2, GCCollectionMode.Optimized);
    }
}
 

 
释放非托管资源
 
析构函数:文件流,数据库连接,窗口等释放还是可以的。但是有一定弊端,与C++的析构函数不同,C#析构函数你不知何时运行,未知的总是恐怖的。
public class MyClass
    {
        StreamWriter sw;
        ~MyClass()
        {
            //进行资源的清理
            sw.Close();
        }
        public void Func()
        {
            FileStream fs = new FileStream("D:\\te.txt", FileMode.OpenOrCreate);
            sw = new StreamWriter(fs);
            string str="哈哈";
            sw.Write(str);
        }
    }
 
IDisposable接口:这个好,微软推荐,加V就是不一样。
eg.
Class Student :IDisposable
{
    public void Dispose()
    {}
}
 
如果在处理过程中出现异常,则theInstance没有释放,所以需要try块中finally来释放
ResourceGobbler theInstance = null
try
(
    theInstance = new ResourceGobbler()
    //todo...
finally
{
    if(theInstance != null)
    {
        theInstance.Dispose();
    )
)
 
微软爸爸说,总是finally来释放烦不烦,来一个简洁方式!
using关键字确保在实现IDisosable接口的对象的引用超出作用域时,在该对象上自动调用Dispose()方法。
using(ResourceGobbler theInstance =new ResourceGobbler())
{
    //todo...
}
 

 (注:部分图是借鉴而来)
posted @ 2019-05-07 17:29  不如吃茶去  阅读(142)  评论(0编辑  收藏  举报