C# 托管和非托管的资源《C#高级编程(第10版)
1.资源
托管资源:垃圾回收器(GC)会处理释放(堆栈);
非托管资源:开发人员手动释放(文件句柄、网络连接、数据库连接);
2.后台内存管理
栈:内存是向下填充的,即从高内存地址向低内存地址填充。当数据入栈后,栈指针就会随之调整,以始终指向下一个空闲存储单元(先进后出)。
托管堆:堆上的内存是向上分配的,所以空闲空间在己用空间的上面。
引用数据类型:把一个引用变量的值赋予另一个相同类型的变量,就有两个变量引用内存中的同一对象了。当一个引用变量超出作用域时,它会从栈中删除,但引用对象的数据仍保留在堆中,一直到程序终止,或垃圾回收器删除它为止,而只有在该数据不再被任何变量引用时,它才会被删除。
3.垃圾回收
托管堆的回收:垃圾回收器释放所有对象后就会把其他对象移动回堆的端部,再次形成一个连续的内存块,堆就可以继续像栈那样确定在什么地方存储新对象,这个压缩操作是托管的堆与非托管的堆的区别所在。使用托管的堆,就只需要读取堆指针的值即可,而不需要遍历地址的链表,来查找一个地方放置新数据。
垃圾回收提高性能的方式:
(一)新对象放置位置固定。创建对象时会把这些对象放在托管堆上,堆的第一部分称为第0代。第一次回收之后,保留的对象压缩之后移动到堆的下一部分(第1代),第0代释放。第二次回收之后,0移动到1,1移动到2,以此类推,0始终空闲等待新对象。最新的对象通常是可以回收的对象,而且可能也会回收大量比较新的对象。如果这些对象在堆中的位置是相邻的,垃圾回收过程就会更快。另外,相关的对象相邻放置也会使程序执行得更快。
(二)大对象堆。使用大于85 000 个字节的对象时,它们就会放在这个特殊的堆上,而不是主堆上。在堆上压缩大对象是比较昂贵的,因此驻留在大对象堆上的对象不执行压缩过程,大对象堆上的回收放在后台线程上进行。
(三)垃圾回收平衡。如果一个钱程使用的内存远远多于其他线程,导致垃圾回收,其他线程可能不需要垃圾回收从而影响效率。垃圾回收过程会平衡这些小对象堆和大对象堆。进行这个平衡过程,可以减少不必要的回收。
为了利用包含大量内存的硬件,垃圾回收过程添加了669b58282dc58bd63186bceb19e8b8f67d1cefb1tencyMode 属性。把这个属性设置为LatencyMode枚举的一个值,可以控制垃圾回收器进行回收的方式。
强引用:垃圾回收器不能回收仍在引用的对象的内存(强引用)。
弱引用:用WeakReference类创建,使用构造函数,可以传递强引用。在使用WeakReference时可以检查IsAlive 属性,再次使用该对象时,WeakReference的Target属性就返回一个强引用。如果属性返回的值不是null,就可以使用强引用。因为对象可能在任意时刻被回收,所以在引用该对象前必须确认它存在。成功检索强引用后,可以通过正常方式使用它,不能被垃圾回收。
4.处理非托管资源
(一)析构函数
在讨论C#中的析构函数时,在底层的.NET 体系结构中,这些函数称为终结器(finalizer)。在C#中定义析构函数时,编译器发送给程序集的实际上是Finalize() 方法.在垃圾回收器销毁对象之前,也可以调用析构函数,但由于使用C#垃圾回收器的工作方式,无法确定C#对象的析构函数何时执行。所以,不能在析构函数中放置需要在某一时刻运行的代码,也不应寄望于析构函数会以特定顺序对不同类的实例调用。如果对象占用了宝贵而重要的资源,应尽快释放这些资源,此时就不能等待垃圾回收器来释放了。
(二)IDisposable接口
Dispose()方法的实现代码显式地释放由对象直接使用的所有非托管资源,并在所有也实现IDisposable 接口的封装对象上调用Dispose()方法。
(三)using语句
using语旬的后面是一对圆括号,其中是引用变量的声明和实例化,该语句使变量的作用域限定在随后的语句块中。另外,在变量超出作用域时,即使出现异常,也会自动调用其Dispose()方法。
IDisposable和终结器的规则:
?如果类定义了实现IDisposable的成员,该类也应该实现IDisposable;
?实现IDisposable 不一定需要实现一个终结器。终结器会带来额外的开销,只在需要时才应该实现终结器,例如,发布本机资源。要释放本机资源,就需要终结器。
?如果实现了终结器,也应该实现IDisposable 接口。这样,本机资源可以早些释放,而不仅是在GC找出被占用的资源时,才释放资源。
?在终结器的实现代码中,不能访问己终结的对象,终结器的执行顺序是没有保证的。
?如果所使用的一个对象实现了IDisposable 接口,就在不再需要对象时调用Dispose 方法。如果在方法中使用这个对象,using语句比较方便。如果对象是类的一个成
员,就让类也实现IDisposable。
5.unsafe代码
指针:指针只是一个以与引用相同的方式存储地址的变量。其区别是C#不允许直接访问在引用变量中包含的地址。“向后兼容性”(调用Windows API等)和“性能”是使用指针主要原因,指针使用起来比较困难,需要非常高的编程技巧和很强的能力,仔细考虑代码所完成的逻辑操作,才能成功地使用指针。
unsafe关键字:因为使用指针会带来相关的风险,所以C#只允许在特别标记的代码块中使用指针。
指针转换:指针实际上存储了一个表示地址的整数,因此任何指针中的地址都可以和任何整数类型之间相互转换。指针到整数类型的转换必须是显式指定的,隐式的转换是不允许的。
void指针:如果要维护一个指针,但不希望指定它指向的数据类型,就可以把指针声明为void,void 指针的主要用途是调用需要void*参数的API函数。