zoukankan      html  css  js  c++  java
  • 第5章 托管和非托管的资源

    • 1.什么是托管与非托管?

    托管资源:一般是指被CLR(公共语言运行库)控制的内存资源,这些资源由CLR来管理。可以认为是.net 类库中的资源。

    非托管资源:不受CLR控制和管理的资源。

    对于托管资源,GC负责垃圾回收。对于非托管资源,GC可以跟踪非托管资源的生存期,但是不知道如何释放它,这时候就要人工进行释放。

    2. 后台内存管理

    介绍给变量分配内存时在计算机的内存中发生的情况

    (1) 值数据类型:

    • 不是对象成员的值数据类型采用栈存储方式。
    • 栈指针表示栈中下一个空闲存储单元的地址。
    • 栈存储是从高内存地址向低内存地址填充。
    • 值数据变量超出作用域时,CLR就知道不再需要这个变量。释放变量时,其顺序总是与给它们分配内存的顺序相反。

    (2) 引用数据类型:

    • 用new运算符请求的内存空间(即托管堆)。用于存储一些数据,在方法退出后很长一段时间内数据仍可用。
    • 托管堆是在垃圾回收器GC的控制下工作。
    • 堆工作原理及内存分配:
    void DoWork()
    {
         Customer arabel;           // 在栈上给这个应用分配存储空间,但仅是一个引用,占用4个字节的空间,而不是实际的Customer对象。
         arabel = new Customer();   // 首先分配堆上的内存,以存储Customer对象;再把变量arabel的值设置为分配给新Customer对象的内存地址。
         // Customer实例没有放在栈中,而是放在堆中
         // 与栈不同,堆上的内存是向上分配的
    //------------------------------------------------------------------ Customer otherCustomer2 = new EnhancedCustomer(); //用一行代码在栈上为otherCustomer2引用分配空间,同时在堆上为EnhancedCustomer对象分配空间。 }
    • 引用变量赋值:把一个引用变量的值赋予另一个相同类型的变量,则有两个变量引用内存中的同一个对象。
    • 引用变量回收:当一个引用变量超出作用域是,它会从栈中删除引用,但引用对象的数据仍保留在堆中;一直到程序终止,或GC删除它为止;只有在该数据不再被任何变量引用时,它才会被删除。

    (3) 垃圾回收

    • GC运行时,会从堆中删除不再引用的所有对象。
    • GC压缩操作:对于托管堆,在删除了能释放的所有对象后,就会把其他对象移动回堆的顶端,再次形成一个连续的内存块。
    • 堆系列:
      • 第0代:创建新对象时,会把它们移动到堆的这个部分中;驻留最新的对象;对象会继续放在这个部分,知道垃圾回收过程第一次进行回收。
      • 第1代:第一次回收清理过程之后,仍保留的对象会被压缩,并移动到该部分。此时第0代对应的部分为空。
      • 第2代:对于第1代中的老对象,这样的移动会再次发生,则其遗留下来的对象会移动到堆的第2代。位于第0代的对象会移动到第1代,第0代仍用于放置新对象。
      • 在给对象分配内存空间时,如果超出了第0代对应的部分的容量,或调用GC.Collect()方法,就会进行垃圾回收。
    • 有助于提高性能的领域:
      • 大对象堆(大于85 000个字节的对象):架构处理堆上较大对象的方式,其对象不执行压缩过程。第2代和大对象堆的回收放在后台线程上进行,即应用程序线程仅会为第0代和第1代的回收而阻塞,从而减少了总暂停时间。
      • 垃圾回收的平衡:专用于服务器的垃圾回收。平衡小对象堆和大对象堆,可以减少不必要的回收。

    3. 强引用和弱引用

    • 强引用:GC不能回收仍在引用的对象的内存。
      • 缺点是强引用实例超出作用域,或指定为null。如果GC在运行,很容易错过引用的清理,不能释放引用的内存。可使用WeakReference避免这种情况。
    • 弱引用:使用WeakReference类创建。使用构造函数,可以传递强引用(Target属性)。
      • 弱引用对象可能在任意时刻被回收,因此引用该对象前必须确认存在(IsAlive属性为true)。成功检索强引用后,可以通过正常方式使用它。
    //创建一个DataObject,并传递构造函数返回的弱引用
    var myWeakReference = new WeakReference(new DataObject());
    
    if (myWeakReference.IsAlive)
    {
        DataObject strongReference = myWeakReference.Target as DataObject;
        if (strongReference != null)
        {
             //使用强引用对象 strongReference
        }    
    }
    else
    {
         // 引用不可用
    }

    4. 处理非托管的资源

    • GC不知道如何释放非托管资源(如文件句柄、网络连接和数据库连接)
    • 托管类在封装对非托管资源的直接或间接引用时,需制定专门的规则,确保非托管的资源在回收类的一个实例时释放。
    • 自动释放非托管资源的机制:
      • 声明一个析构函数(或终结器),作为类的一个成员
      • 在类中实现System.IDisposable接口

    (1) 析构函数或终结器

    • 在C#中定义析构函数时,编译器发送给程序集的实际上是Finalize()方法
    • C#析构函数问题
      • 不确定性:由于GC的工作方式,无法确定C#对象的析构函数何时执行。
      • 会延迟对象最终从内存中删除的时间。
        • 没有析构函数的对象:在GC的一次处理中从内存删除
        • 有析构函数的对象:需要两次才能销毁。

    (2) IDisposable接口:推荐使用,为释放非托管的资源提供了确定的机制,精确控制何时释放资源。

    • 一般方式
    SqlConnection conn = null;
    try
    {
        conn = new SqlConnection();
        //do something;
    }
    finally
    {
        conn?.Dispose();
    }
    • 改进方式,使用using简化输入,编译器自动翻译成 try...finally。在变量超出作用域是,会自动调用其Dispose()方法。
    using(SqlConnection conn = new SqlConnection())
    {
          //do something;
    }

    (3) 双重实现:正确调用Dispose(),同时将析构函数作为一种安全机制。

        public class BaseResource : IDisposable
        {
            private IntPtr _handle; // 句柄,属于非托管资源
            private System.ComponentModel.Component _comp; // 组件,托管资源
            private bool _isDisposed = false; // 是否已释放资源的标志
    
            //实现接口方法
            //由类的使用者,在外部显示调用,释放类资源
            public void Dispose()
            {
                Dispose(true);// 释放托管和非托管资源
    
                // 将对象从垃圾回收器链表中移除,
                // 从而在垃圾回收器工作时,只释放托管资源,而不执行此对象的析构函数
                GC.SuppressFinalize(this);
            }
    
            //由垃圾回收器调用,释放非托管资源
            ~BaseResource()
            {
                Dispose(false);// 释放非托管资源
            }
    
            //参数为true表示释放所有资源,只能由使用者调用
            //参数为false表示释放非托管资源,只能由垃圾回收器自动调用
            //如果子类有自己的非托管资源,可以重载这个函数,添加自己的非托管资源的释放
            //但是要记住,重载此函数必须保证调用基类的版本,以保证基类的资源正常释放
            protected virtual void Dispose(bool disposing)
            {
                if (!this._isDisposed)// 如果资源未释放 这个判断主要用了防止对象被多次释放
                {
                    if (disposing)
                    {
                        // 释放托管资源,调用其Dispose方法 
                        _comp.Dispose();
                    }    
    
                    // 释放非托管资源
                    closeHandle(_handle);
                    _handle= IntPtr.Zero;         
                }
                this._isDisposed = true; // 标识此对象已释放
            }
        }

    5.不安全的代码:C#直接访问内存

    (1) 用指针直接访问内存:

    • 引用就是一个类型安全的指针。
    • 使用指针可以访问实际内存地址,执行新类型的操作。
    • 使用指针的原因:
      • 向后兼容性:用于调用本地的Windows API => 可使用DllImport声明,以避免使用指针。
      • 性能:指针提供最优速度性能 => 可使用代码配置文件,查找代码中的瓶颈。
    • 使用指针,必须授予代码运行库的代码访问安全机制的高级别信任。
    • 强烈建议不要轻易使用指针。

     (2) 用unsafe关键字编写不安全的代码

    • 方法、方法的参数、类或结构、成员,均可使用unsafe
    • 方法中的一块代码可标记为unsafe
    void MyMethod()
    {
         // code that doesn't use pointers
         unsafe
         {
               // unsafe code that uses pointers here
         }
         // more 'safe' code that doesn't use pointers
    }
    •  不能把局部变量本身标记为unsafe

    (3) 指针的语法

    • 把代码块标记为unsafe后,可以使用指针语法声明。
    • 指针运算符
      • & 寻址运算符:表示“取地址”,并把一个值数据类型转换为指针。
      • * 间接寻址运算符:表示“获取地址的内容”,把一个指针转换为值数据类型。
    • 指针可以声明为任意一种值类型,包括结构;但不能声明为类或数组。

    (4) 将指针强制转换为整数类型

    • 由于指针实际上存储了一个表示地址的整数,因此任何指针中的地址都可以和任何整数类型之间相互转换。
    • 转换必须是显示指定的。
    • 转换的主要目的是显示指针地址
    • 32位系统,可转换为uint、long、ulong
    • 64位系统,可转换为ulong

    (5) 指针类型之间的强制转换

    • 可以在指向不同类型的指针之间进行显式转换
    • 指针类型之间转换,实现C union类型的等价形式

    (6) void指针

    • 维护一个指针,但不希望指定它指向的数据类型,可声明为void指针
    • 主要用途:调用需要void*参数的API函数

    (7) 指针算术运算

    • 指针加减整数,是指指针存储地址值的变化。
    • 不同类型的指针字节数不同
      • 给类型为T的指针加上数值X,其中指针的值为P,则得到的结果是P+X*(sizeof(T))
      • 如果类型是byte或char,总字节数不是4的倍数,连续值不是默认地存储在连续的存储单元中
    • 对指针使用+、-、+=、-=、++、--,其右边变量必须是long或ulong
    • 不允许对void指针执行算术运算

    (8)sizeof运算符:确定各种数据类型的大小。

    • 优点是不必在代码中硬编码数据类型的大小
    sizeof(char) = 2; sizeof(bool) = 1;
    • 结构可以使用,类不能使用。

    (9)结构指针:指针成员访问运算符

    • 结构不能包含任何引用类型,因为指针不能指向任何引用类型。
    • 指针成员访问运算符: ->
    // 结构
    struct MyStruct
    {
         public long X;
         public float F;  
    }
    
    // 结构指针
    MyStruct* pStruct;
    
    // 初始化
    var myStruct = new MyStruct();
    pStruct = &myStruct;
    
    // 通过指针访问结构的成员值
    (*pStruct).X = 4;
    (*pStruct).F = 3.4f;
    // 使用成员访问运算符
    pStruct->X = 4;
    pStruct->F = 3.4f;
    
    //指针指向结构中一个字段
    long* pL = &(pStruct->X);
    float* pF = &(pStruct->F);

    (10) 类成员指针

    • 对类中值类型成员创建指针,需要使用fixed关键字,告知GC这些对象不能移动。
      • 在执行fixed块中代码时,不能移动对象位置。
      • 声明多个fixed指针,就可以在同一个代码块钱放置多条fixed对象
      • 可以嵌套fixed块
      • 类型相同,可在同一个fixed块中初始化多个变量

    (11)使用指针优化性能:创建基于栈的数组。

    • 在栈中的数组具有高性能、低系统开销的特点,但只对于一位数组比较简单。
    • 关键字stackalloc:指示在栈上分配一定量的内存。
      • 要存储的数据类型
      • 要存储的数据项数
    int size; 
    size = 20; 
    double* pDoubles = stackalloc double[size];
      • 分配的字节数是项数乘以sizeof(数据类型)
      • stackalloc返回的指针指向新分配内存块的顶部
      • 数组元素访问:
        • 使用指针算术,即表达式*(pDouble+X)访问数组中下标为X的元素
        • 表达式pDouble[X]在编译时解释为*(pDouble+X)

    6.平台调用

    •  并不是Windows API调用的所有特性都可用于.NET Framework,可采用平台调用方法实现。
    • 采用extern修饰符标记
    • 用属性[DllImport]引用DLL
    • 非托管方法定义的参数类型必须用托管代码映射类型
    • C++有不同Boolen数据类型,可使用特性[MarshalAs]指定.NET类型bool应映射为哪个本机类型
    // C++ 调用Windows API(kernel32.dll)中CreateHardLink
    BOOL CreateHardLink(
           LPCTSTR lpFileName,
           LPCTSTR lpExistingFileName,
           LPSECURITY_ATTRIBUTES lpSecurityAttributes
    );
    
    // C# 调用CreateHardLink
    [DllImport("kernel32.dll", SetLastError="true",
                         EntryPoint="CreateHardLink", CharSet=CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CreateHardLink(string newFileName,
                string existingFilename,
                IntPtr securityAttributes);
    • 通常本地方法时,通常必须使用Windows句柄(IntPtr结构);NET2.0引入SafeHandle类,派生的句柄类型是SafeFileHandleSafeWaitHandleSafeNCryptHandleSafePipeHandle。
  • 相关阅读:
    CGO入门和OCR文字识别(非第三方API,有源码,效果好)实战
    Golang中如何正确的使用sarama包操作Kafka?
    音量强度转分贝db
    ShowDialog()弹出的窗体,关闭后,主窗体会闪烁的BUG
    小鱼提问3 static方法中可以访问某个类的私有变量吗(不通过反射的其他非正常手段)?什么情况下可以?
    “-="的陷阱
    c++ 从一个BYTE[] *filePtr 追加二进制文件
    Android解决程序切换后台被干掉,恢复状态问题
    Cookie
    dede后台搜索标签
  • 原文地址:https://www.cnblogs.com/zhangjbravo/p/9546836.html
Copyright © 2011-2022 走看看