C#教程(4)-----托管和非托管的资源(更新中......)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_43679333/article/details/98031813

一、资源

资源是一个反复被利用的术语。术语”资源“的一个用法是本地化。在本地化中,资源用于翻译文本和图像。”资源“的另一个用法用于主题:使用托管和非托管的资源——储存在托管和本机堆中的资源。尽管垃圾收集器释放储存在托管堆中的托管对象,但不释放本机堆中的对象。必须由开发人员自己释放他们。

二、后台内存管理

虽然C#编程不需要担心具体的内存管理,但理解程序如何在后台管理内存有助于提高性能和速度。

1.值数据类型

windows使用一个虚拟寻址系统,把程序的可用内存地址映射到真实的内存地址上。实际上每个进程都使用4GB的内存(32位处理器)。这个4GB包含了程序的所有代码,dll和变量空间等。被称为虚拟地址空间或虚拟内存。
4GB中的每一个存储单元都是从0往上排序。要访问内存中的某个值就要提供表示这个空间的一个数字。编译器将便于人们理解的变量转换为处理器可以理解的内存地址。
在虚拟内存中,有一个区域称为栈。工作原理,先进后出。(如图)
在这里插入图片描述

{
	int a;
	{
		int b;
	}
}

在一段程序中先定义a再定义b,程序结束时会先释放b,再释放a,b的生命周期完全包含在a中。同时b在另一个代码块中(花括号),所在它在另一个作用域中。这称为块作用域或结构作用域。
栈指针表示栈中下一个空闲存储单元的地址。栈是由上向下填充。当数据进栈后,栈指针就会随之进行调整。
例如:

			int a = 10;
            double b = 30.0;

在这里插入图片描述
int a = 10;之后为a分配4个字节,即79999~ 79996;double占8个字节,即79995~ 79988;

2. 引用数据类型

堆,全称托管堆。是处理器可用内存的另一个内存区域。由下向上分配

假设有类Customer
Customer a;
a = new Customer();

代码执行到Customer a;在栈上给Customer分配4个自接的空间,但这只是一个引用。执行到a = new Customer();时,会在堆中给a分配一个适当大小的空间已存储Customer对象,并将栈中的a的值设置为分配给Customer对象的内存地址。

3.垃圾回收

在这里插入图片描述
堆中不会出现这种情况,因为垃圾回收器会将其他对象移动到堆的端部,再次形成一个连续的内存块。这就是托管和非托管的堆的区别。使用托管堆就只需要读取堆指针的值计科,不需要遍历链表来查找一个地方放置数据。
注意:可以调用System.GC.Collect()强迫垃圾回收器在某个地方运行。

创建对象时,会将对象放在托管堆上。堆的第一部分称为第0代。创建新对象时,会将他移动到这个部分。因此这里驻留了最新的对象。

垃圾回收过程中,遗留的旧对象会进行压缩放在第1代对应的部分上。再次进行垃圾回收时,老对象的这种移动会再次发生,这意味着第一代变为第二代,第0代对象移动到第1代,第0带仍用来保存新对象。
注意:在给对象分配空间时,如果超过了第0代对应的部分的容量,就会进行垃圾回收。

这个过程会极大的提高应用程序的性能。一般而言,新创建的对象都是可以进行回收的,而且可能回收大量比较新的对象。如果这些对象是相邻的也会使应用程序执行速度变快。

在.NET中,垃圾回收提高性能的另一领域是架构处理堆上较大对象的方式。在.NET中,大于85000个字节的对象有自己的托管堆,而不是在主堆上,称为大托管堆。

在进一步改进垃圾回收后,应用程序线程只会对第一代和第0代对象实施回收,而其他代放在后台执行,缩短时间。

有助于垃圾回收的另一项优化是垃圾回收的平衡,它用于服务器的垃圾回收。对于服务器,每一个逻辑服务器都有一个垃圾回收堆。因此一个垃圾回收堆用尽了内存时,会调用垃圾回收程序,所有其他堆也会触发垃圾回收程序,这就造成浪费。垃圾回收会平衡这些堆——小对象堆和大对象堆。推进这个平衡过程,可以减少不必要的回收。

三、强引用和弱引用

垃圾回收器不能回收仍在使用的对象——强引用。可以回收不在根表直接或间接引用的托管内存,但是可能会忘记释放引用。

注意:如果对象相互引用——A引用B,B引用C,C引用A,则GC可能会销毁所有对象。

计入有一个对象MyClass,并创建一个变量myclassVariable来引用它。那么在这个变量的作用域内,就存在对MyClass的强引用

var myclassVariable = new MyClass();

这意味着垃圾回收器不能回收MyClass对象使用的内存。可以穿件一个缓存对象,它引用myclassVariable对象。

			var myCache = new MycaChe();
            myCache.Add(myclassVariable);

用完myclassVariable,可以将他指定为NULL

myclassVariable = null;

现在如果运行垃圾回收器,就不能释放myclassVariable引用的内存,因为在缓存对象中使用。但这样的引用很容易被忘记,使用WeakRaference避免。

注意:使用事件很容易错过引用的清理。此时也可以使用弱引用

弱引用是使用WeakReference类创建的。使用构造函数,可以传递强引用。

var myWeakReference = new WeakReference(new dataobject);

            if (myWeakReference.IsAlive)
            {
                dataobject strongReference = myWeakReference.Target as dataobject;
                if (strongReference != null)
                {
                    //use the strongReference
                }
                else
                {
                    //reference not available
                }
            }

【弱引用这块没怎么看懂,以后重新编写此处,会的小伙伴可以教我一下吗】

四、处理非托管的资源

C#编程中,不需要担心不再需要的对象,垃圾回收器会完成这项任务。但是垃圾回收器不知带如何处理非托管资源,例如:文件句柄、网络连接和数据库连接。托管类在封装对非托管资源的引用时要制定专门的规则,确保回收实例时释放。
两种机制来解决这一问题:

  • 声明析构函数
  • 实现System.IDisposable接口

1.析构函数或终结器

C#在执行析构函数时,它会隐式的将析构函数古代码转换为等驾驭重写Finallize()方法的代码,从而确保执行父类的Finallize()方法;
C#使用析构函数比C++次数少的原因:

  1. C#析构函数不稳定
  2. 延迟对象从内存销毁的时间
  3. 对性能的影响显著

2.IDisposable接口

C#用IDisposable接口代替析构函数,不带参数,返回void。

假设有有一个A类需要使用外部资源,使用它并销毁他:

			var a = new A();

            //do somesthing

            a.Dispose();

如果怕出现异常:

			 A a = null;
            try
            {
                a = new A();

                //do somesthing
            }
            finally
            {
                a?.Dispose();
            }

3.using语句

使用异常处理,即使异常也能确保调用了Dispose()方法,但重复这样的结构,代码容易混淆。可以使用using语句,当实现IDisposable接口的语句的对象引用超出作用域时就会自动调用Dispose()方法。

using (var a = new A())
            {
                //do somesthing
            }

4.实现IDisposable接口和析构函数

举一个实现IDisposable接口和析构函数的例子:

	public class UserPreferences:IDisposable
    {
        private bool _idDisposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_idDisposed)
            {
                if (disposing)
                {
                    ///
                }
                ///
            }
            _idDisposed = true;
        }

        ~UserPreferences()
        {
            Dispose(false);
        }

        public void SomeMethod()
        {
            if (_idDisposed)
            {
                throw new ObjectDisposedException("ResourceHolder");
            }
        }
      }

上述例子中Dispose有第二个重构方法,这是真正完成清理工作的方法,由析构函数调用。
GC.SuppressFinalize(this);表示该对象已经清理,不需要再次清理。

5.IDisposable和终结器的规则

  • 如果类定义实现了IDisposable的成员,也应该实现IDisposable;
  • 实现IDisposable并不意味着实现终结器。要是香释放本机资源,就需要终结器。
  • 如果实现了终结器,也应该实现IDisposable接口。
  • 在终结器的实现代码中,不能访问已终结的对象

五、不安全的代码

1.用指针直接访问内存

2.指针示例:PointerPlayground

3.使用指针优化性能

六、平台调用

猜你喜欢

转载自blog.csdn.net/qq_43679333/article/details/98031813