澳门新萄京官方网站-www.8455.com-澳门新萄京赌场网址

澳门新萄京官方网站:net垃圾回收,对象生命周

2019-10-05 作者:www.8455.com   |   浏览(52)

在C#中,程序员无法直接在C#中删除一个托管对象,因为C#不提供这个功能,那么类的实例就需要通过CLR调用垃圾回收机制进行清除,回收内存。.NET垃圾回收器会压缩空的内存块来实现优化,为了辅助这一功能,托管堆会保存一个指针,它指向下一个对象将被分配的位置。那么CLR是如何使用垃圾回收机制呢?首先,类实例化之后具体的对象会被分配到一块叫托管堆的内存区域上,将托管堆中的对象的引用地址返回给函数中的引用变量,引用变量保存在栈内,要使用对象中的方法只需要使用点操作就可以。特别需要说一下的是,结构是数值类型,它直接分配在栈上(所以的数值类都是这样的,只有引用类型才会保存在托管堆上)。实例化结束之后,垃圾回收器会在一个对象从代码库的任何部分都不可访问的时候,将它从堆中删除,例:

《精通C#》第十三章 对象的生命周期,

在C#中,程序员无法直接在C#中删除一个托管对象,因为C#不提供这个功能,那么类的实例就需要通过CLR调用垃圾回收机制进行清除,回收内存。.NET垃圾回收器会压缩空的内存块来实现优化,为了辅助这一功能,托管堆会保存一个指针,它指向下一个对象将被分配的位置。那么CLR是如何使用垃圾回收机制呢?首先,类实例化之后具体的对象会被分配到一块叫托管堆的内存区域上,将托管堆中的对象的引用地址返回给函数中的引用变量,引用变量保存在栈内,要使用对象中的方法只需要使用点操作就可以。特别需要说一下的是,结构是数值类型,它直接分配在栈上(所以的数值类都是这样的,只有引用类型才会保存在托管堆上)。实例化结束之后,垃圾回收器会在一个对象从代码库的任何部分都不可访问的时候,将它从堆中删除,例:

static void MakeCar()

{

Car mycar=new Car();

}

在例子中,Car的引用mycar直接在MakeCar中创建,并没有被传到该方法以外的作用域的,因此,在这个方法调用结束之后,这个对象就不会再被访问,此时它就是垃圾回收器的回收目标,但是,要知道,一个对象在失去意义之后并不会立即被删除,CLR调用垃圾回收器的标准是:在创建对象时,先判断对象所需要的内存的大小,在判断目前的托管堆是否有足够的内存保存它,当托管堆没有足够的内存时,CLR就会调用垃圾回收器进行垃圾回收,因此,在对象失去意义之后还需要等待CLR调用垃圾回收器时才能被删除。那么CLR是如何判断对象所需的内存,以及堆的内存是否够用?当C#编译器在遇到new关键字时,它会自动在方法的实现中加上一条CIL newobj 的指令,它会计算分配对象所需的总内存,检查堆的内存空间,如果内存足够,则调用类型的构造函数,最终将内存中的新变量的引用返回给调用者,它的地址是下一个对象指针的最后位置,若是内存不足,则CLR调用垃圾回收器释放内存。在回调地址之后,就将对象的指针指向下一个可用的地址。那么垃圾回收器如何判断一个对象是否还在使用,这就要介绍一个应用程序根,所谓的根,说白了就是存储堆上的对象的引用地址的存储位置,在回收过程中运行库会对对象进行判断,判断程序是否还可以访问它们,也就是说它们的根是否还存在,若是不存在,则标记为垃圾,进而被清除对象,CLR压缩内存,指针指向正确的位置。但是若是每一次进行垃圾回收的时候都要对托管堆上的所以数据进行一次判定,这种方法就太过于耗时耗力了,于是就有了代,代的设计思路是:对象在堆上存在的时间越长就越可能被保留。所以就将堆上的对象共分为0-2的3代,垃圾回收器在运行的时候,首先会检测0代的对象,并且将那些不需要的对象释放,空出内存,若是空出的内存不够,则会往上面一级的1级代进行检测,以此类推,直到获取所需要的内存大小为止,而在这之后,0代上没被删除的对象就会被标记为1代,一代中未被删除的对象就标记为2代,但是2代还是二代,因为这是上限。在这里还得说一下,其实垃圾回收器使用的两个堆,我们所说的是小对象堆,还有一个大对象堆,他存储的是大于85k的对象,因为它的内容太大,对它进行修改的话,花费代价太大,所以在垃圾回收器极少会对它进行修改。

以上是垃圾回收器自动对对上面的数据进行回收,这并不需要人为的进行操控,但是这是对于托管在托管堆上面的对象,若是有些数据不是托管的资源呢?.NET提供了一个System.GC的类类型,它可以通过编程使用一些静态成员与垃圾回收器进行交互,这种行为也叫作强制垃圾回收,它可以由我们自己决定什么时候释放某个对象的资源,而不用被动等待垃圾回收器运行,一般来说在不希望接下来的代码被垃圾回收器打断的运行时候(垃圾回收器的运行时间是不确定的),或者我需要一次性分配很多的对象的时候都会用到强制回收,强制回收使用GC.Collect()方法,在它的后面必须要调用GC.WaitForPendingFinalize(), 另外,使用Finalize()构建可终结对象,当应用程序的应用程序域从内存中卸载的话,CLR就会自动调用它的生命周期中所创建的每一个可终结对象的终结器进行强制回收,重写Finalize()无法像普通的类一样,它需要类似C 的析构语法,此外终结器还需在名称之前加~,他不接受访问修饰符,不接受参数,不支持重载,但是使用这种方式的话,需要两次的来及回收才能真正的释放该资源,并且由于是额外的处理,所以速度回变得非常慢。所以就可以考虑构建一个可处置对象,构建可处置对象需要实现IDisposable,这个方法不止可以释放一个对象的非托管资源,而且还可以对任何它包含的可处置对象调用Dipose(),使用这种方法可以有自己调用释放内存,若是忘记调用,也会有垃圾回收器进行释放,所以这种方式在我看来会更安全好用一些。在C#类中还为实现了IDisposable的接口提供了一类语法:using,使用这种语法的好处在于,可以由Dispose()调用扩展try/catch结构,例如:using(class c=new class()){}在编译之后,与class c=new class();try{}catch(){};是等同的。

在这一个章节内,我觉得还有一个非常使用的泛型类Lazy<>,但凡被这个类所定义的数据在代码库实际使用它之前是不会被创建的,这样就可以使一些不常使用的大数据在实例化对象的时候不同时被创建,进而占用内存空间。例:

class song{

澳门新萄京官方网站,public string Artist{get;set;}

public string TrackName{get;set;}

public double TrackLength{get;set;}

}

class AllTracks

{

private Song[] allSongs=new Song[10000];

public AllTracks()

{

Console.WriteLine();

}

class MediaPlayer()

{

public void Play(){};

private AllTracks allsongs=new AllTracks();

public AllTracks GetAllTracks()

{

return allSongs;

}

}

}

main(){

MediaPlayer m=new MediaPlayer();//在这个时候,已经间接的创建10000个歌曲对象了

}

若是将MediaPlayer修改为:

class MediaPlayer()

{

public void Play(){};

private Lazy<AllTracks> allsongs=new Lazy<AllTracks>();

public AllTracks GetAllTracks()

{

return allSongs.Value;

}

}

调用方式就要改为:

main(){

MediaPlayer m=new MediaPlayer();//在这个时候,未创建10000个歌曲对象了

AllTracks songs=m.GetAllTracks();//此时,歌曲对象才创建

}

对象的生命周期, 在C#中,程序员无法直接在C#中删除一个托管对象,因为C#不提供这个功能,那么类的实例就需要通过...

 

本文引自:

static void MakeCar()

  • GC 垃圾回收

在开发.NET程序过程中,由于CLR中的垃圾回收(garbage collection)机制会管理已分配的对象,所以程序员就可以不用关注对象什么时候释放内存空间了。但是,了解垃圾回收机制还是很有必要的,下面我们就看看.NET垃圾回收机制的相关内容。

{

     .NET Framework 的垃圾回收器管理应用程序的内存分配和释放。每次您使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。只要托管堆中有地址空间可用,运行库就会继续为新对象分配空间。但是,内存不是无限大的。最终,垃圾回收器必须执行回收以释放一些内存。垃圾回收器优化引擎根据正在进行的分配情况确定执行回收的最佳时间。当垃圾回收器执行回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。在内存大于 2GB 的服务器中,可能需要在 boot.ini 文件中指定 /3GB 开关,以避免当内存仍可供系统使用时出现明显的内存不足问题。当使用非托管资源时,需要构造一个用完后清理自身的类,这时需要编写代码来进行垃圾回收。

创建对象

在C#中,我们可以通过new关键字创建一个引用类型的对象,比如下面一条语句。New关键字创建了一个Student类型的对象,这个新建的对象会被存放在托管堆中,而这个对象的引用会存放在调用栈中。(对于引用类型可以查看,C#中值类型和引用类型)

Student s1 = new Student();

在C#中,当上面的Student对象被创建后,程序员就可以不用关心这个对象什么时候被销毁了,垃圾回收器将会在该对象不再需要时将其销毁。

当一个进程初始化后,CLR就保留一块连续的内存空间,这段连续的内存空间就是我们说的托管堆。.NET垃圾回收器会管理并清理托管堆,它会在必要的时候压缩空的内存块来实现优化,为了辅助垃圾回收器的这一行为,托管堆保存着一个指针,这个指针准确地只是下一个对象将被分配的位置,被称为下一个对象的指针(NextObjPtr)。为了下面介绍垃圾回收机制,我们先详细看看new关键字都做了什么。

Car mycar=new Car();

  • 将对象引用设置为空

new关键字

当C#编译器遇到new关键字时,它会在方法的实现中加入一条CIL newobj命令,下面是通过ILSpy看到的IL代码。

IL_0001: newobj instance void GCTest.Student::.ctor()

其实,newobj指令就是告诉CLR去执行下列操作:

  • 计算新建对象所需要的内存总数
  • 检查托管堆,确保有足够的空间来存放新建的对象

    • 如果空间足够,调用类型的构造函数,将对象存放在NextObjPtr指向的内存地址
    • 如果空间不够,就会执行一次垃圾回收来清理托管堆(如果空间依然不够,就会报出OutofMemoryException)
  • 最后,移动NextObjPtr指向托管堆下一个可用地址,然后将对象引用返回给调用者

按照上面的分析,当我们创建两个Student对象的时候,托管堆就应该跟下图一致,NextObjPtr指向托管堆新的可用地址。

澳门新萄京官方网站 1

托管堆的大小不是无限制的,如果我们一直使用new关键字来创建新的对象,托管堆就可能被耗尽,这时托管堆可以检测到NextObjPtr指向的空间超过了托管堆的地址空间,就需要做一次垃圾回收了,垃圾回收器会从托管堆中删除不可访问的对象

}

     在C#中将对象引用设置为空并不意味着强制垃圾回收立即启动,唯一实现的是显示的取消了引用和之前所指向对象之间的连接,不管怎么样,这么做也不会有什么害处。

应用程序的根

垃圾回收器是如何确定一个对象不再需要,可以被安全的销毁?

这里就要看一个应用程序根(application root)的概念。根(root)就是一个存储位置其中保存着对托管堆上一个对象的引用,根可以属性下面任何一个类别:

  • 全局对象和静态对象的引用
  • 应用程序代码库中局部对象的引用
  • 传递进一个方法的对象参数的引用
  • 等待被终结(finalize,后面介绍)对象的引用
  • 任何引用对象的CPU寄存器

垃圾回收可以分为两个步骤:

  1. 标记对象
  2. 压缩托管堆

下面结合应用程序的根的概念,我们来看看垃圾回收这两个步骤。

在例子中,Car的引用mycar直接在MakeCar中创建,并没有被传到该方法以外的作用域的,因此,在这个方法调用结束之后,这个对象就不会再被访问,此时它就是垃圾回收器的回收目标,但是,要知道,一个对象在失去意义之后并不会立即被删除,CLR调用垃圾回收器的标准是:在创建对象时,先判断对象所需要的内存的大小,在判断目前的托管堆是否有足够的内存保存它,当托管堆没有足够的内存时,CLR就会调用垃圾回收器进行垃圾回收,因此,在对象失去意义之后还需要等待CLR调用垃圾回收器时才能被删除。那么CLR是如何判断对象所需的内存,以及堆的内存是否够用?当C#编译器在遇到new关键字时,它会自动在方法的实现中加上一条CIL newobj 的指令,它会计算分配对象所需的总内存,检查堆的内存空间,如果内存足够,则调用类型的构造函数,最终将内存中的新变量的引用返回给调用者,它的地址是下一个对象指针的最后位置,若是内存不足,则CLR调用垃圾回收器释放内存。在回调地址之后,就将对象的指针指向下一个可用的地址。那么垃圾回收器如何判断一个对象是否还在使用,这就要介绍一个应用程序根,所谓的根,说白了就是存储堆上的对象的引用地址的存储位置,在回收过程中运行库会对对象进行判断,判断程序是否还可以访问它们,也就是说它们的根是否还存在,若是不存在,则标记为垃圾,进而被清除对象,CLR压缩内存,指针指向正确的位置。但是若是每一次进行垃圾回收的时候都要对托管堆上的所以数据进行一次判定,这种方法就太过于耗时耗力了,于是就有了代,代的设计思路是:对象在堆上存在的时间越长就越可能被保留。所以就将堆上的对象共分为0-2的3代,垃圾回收器在运行的时候,首先会检测0代的对象,并且将那些不需要的对象释放,空出内存,若是空出的内存不够,则会往上面一级的1级代进行检测,以此类推,直到获取所需要的内存大小为止,而在这之后,0代上没被删除的对象就会被标记为1代,一代中未被删除的对象就标记为2代,但是2代还是二代,因为这是上限。在这里还得说一下,其实垃圾回收器使用的两个堆,我们所说的是小对象堆,还有一个大对象堆,他存储的是大于85k的对象,因为它的内容太大,对它进行修改的话,花费代价太大,所以在垃圾回收器极少会对它进行修改。

  • 应用程序根

标记对象

在垃圾回收的过程中,垃圾回收器会认为托管堆中的所有对象都是垃圾,然后垃圾回收器会检查所有的根。为此,CLR会建立一个对象图,代表托管堆上所有可达对象。

澳门新萄京官方网站 2

假设托管堆中有A-G七个对象,垃圾回收过程中垃圾回收器会检查所有的对象是否有活动根。这个例子的垃圾回收过程可以描述如下(灰色表示不可达对象):

  1. 当发现有根引用了托管堆中的对象A时,垃圾回收器会对此对象A进行标记
  2. 对一个根检测完毕后会接着检测下一个根,执行步骤一种同样的标记过程,标记对象B,在标记B时,检测到对象B内又引用了另一个对象E,则也对E进行标记;由于E引用了G,同样的方式G也会被标记
  3. 重复步骤二,检测Globales根,这次标记对象D

代码中很有可能多个对象中引用了同一个对象E,垃圾回收器只要检测到对象E已经被标记过,则不再对对象E内所引用的对象进行检测,这样做有两个目的:一是提高性能,二是避免无限循环

所有的根对象都检查完之后,有标记的对象就是可达对象,未标记的对象就是不可达对象。

以上是垃圾回收器自动对对上面的数据进行回收,这并不需要人为的进行操控,但是这是对于托管在托管堆上面的对象,若是有些数据不是托管的资源呢?.NET提供了一个System.GC的类类型,它可以通过编程使用一些静态成员与垃圾回收器进行交互,这种行为也叫作强制垃圾回收,它可以由我们自己决定什么时候释放某个对象的资源,而不用被动等待垃圾回收器运行,一般来说在不希望接下来的代码被垃圾回收器打断的运行时候(垃圾回收器的运行时间是不确定的),或者我需要一次性分配很多的对象的时候都会用到强制回收,强制回收使用GC.Collect()方法,在它的后面必须要调用GC.WaitForPendingFinalize(), 另外,使用Finalize()构建可终结对象,当应用程序的应用程序域从内存中卸载的话,CLR就会自动调用它的生命周期中所创建的每一个可终结对象的终结器进行强制回收,重写Finalize()无法像普通的类一样,它需要类似C 的析构语法,此外终结器还需在名称之前加~,他不接受访问修饰符,不接受参数,不支持重载,但是使用这种方式的话,需要两次的来及回收才能真正的释放该资源,并且由于是额外的处理,所以速度回变得非常慢。所以就可以考虑构建一个可处置对象,构建可处置对象需要实现IDisposable,这个方法不止可以释放一个对象的非托管资源,而且还可以对任何它包含的可处置对象调用Dipose(),使用这种方法可以有自己调用释放内存,若是忘记调用,也会有垃圾回收器进行释放,所以这种方式在我看来会更安全好用一些。在C#类中还为实现了IDisposable的接口提供了一类语法:using,使用这种语法的好处在于,可以由Dispose()调用扩展try/catch结构,例如:using(class c=new class()){}在编译之后,与class c=new class();try{}catch(){};是等同的。

     根就是一个存储位置,其中保存着对托管堆上一个对象的引用。在垃圾回收过程中,运行库检查堆上的对象,判断应用程序是否仍然可以访问它们,即对象是否还是有根的。

压缩托管堆

继续上面的例子,垃圾回收器将销毁所有未被标记的对象,释放这些垃圾对象所占的内存,再把可达对象移动到这里以压缩堆。

注意,在移动可达对象之后,所有引用这些对象的变量将无效,接着垃圾回收器要重新遍历应用程序的所有根来修改它们的引用。在这个过程中如果各个线程正在执行,很可能导致变量引用到无效的对象地址,所以整个进程的正在执行托管代码的线程是被挂起的。

澳门新萄京官方网站 3

经过了垃圾回收之后,所有的非垃圾对象被移动到一起,并且所有的非垃圾对象的指针也被修改成移动后的内存地址,NextObjPtr指向最后一个非垃圾对象的后面。

在这一个章节内,我觉得还有一个非常使用的泛型类Lazy<>,但凡被这个类所定义的数据在代码库实际使用它之前是不会被创建的,这样就可以使一些不常使用的大数据在实例化对象的时候不同时被创建,进而占用内存空间。例:

    • 根的类别
      • 全局对象的引用(C#中不允许,但CIL代码允许分配全局对象)
      • 静态对象和字段的引用
      • 应用程序代码库中的局部对象引用
      • 传递进一个方法的对象参数的引用
      • 等待被终结的对象的引用
      • 澳门新萄京官方网站:net垃圾回收,对象生命周期。任何引用对象的CPU寄存器

对象的代

当CLR试图寻找不可达对象的时候,它需要遍历托管堆上的对象。随着程序的持续运行,托管堆可能越来越大,如果要对整个托管堆进行垃圾回收,势必会严重影响性能。所以,为了优化这个过程,CLR中使用了"代"的概念,托管堆上的每一个对象都被指定属于某个"代"(generation)。

"代"这个概念的基本思想就是,一个对象在托管堆上存在的时间越长,那么它就更可能应该保留。托管堆中的对象可以被分为0、1、2三个代:

  • 0代:从没有被标记为回收的新分配的对象
  • 1代:在上一次垃圾回收中没有被回收的对象
  • 2代:在一次以上的垃圾回收后仍然没有被回收的对象

下面还是通过一个例子看看代这个概念(灰色代表不可达对象):

  1. 在程序初始化时,托管堆上没有对象,这时候新添到托管堆上的对象是的代是0,这些对象从来没有经过垃圾回收器检查。假设现在托管堆上有A-G七个对象,托管堆空间将要耗尽。

    澳门新萄京官方网站 4

  2. 如果现在需要更多的托管堆空间来存放新建的对象(H、I、J),CLR就会触发一次垃圾回收。垃圾回收器就会检查所有的0代对象,所有的不可达对象都会被清理,所有没有被回收掉的对象就成为了1代对象。

    澳门新萄京官方网站 5

  3. 假设现在需要更多的托管堆空间来存放新建的对象(K、L、M),CLR会再触发一次垃圾回收。垃圾回收器会先检查所有的0代对象,但是仍需要更多的空间,那么垃圾回收器会继续检查所有 的1代对象,整理出足够的空间。这时,没有被回收的1代对象将成为2代对象。2代对象是目前垃圾回收器的最高代,当再次垃圾回收时,没有回收的对象的代数依然保持2。

    澳门新萄京官方网站 6

通过前面的描述可以看到,分代可以避免每次垃圾回收都遍历整个托管堆,这样可以提高垃圾回收的性能。

class song{

System.GC

.NET类库中提供了System.GC类型,通过该类型的一些静态方法,可以通过编程的方式与垃圾回收器进行交互。

看一个简单的例子:

澳门新萄京官方网站 7;)

class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Gender { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));

        Console.WriteLine("This OS has {0} object generations", GC.MaxGeneration);

        Student s = new Student { Id = 1, Name = "Will", Age = 28, Gender = "Male"};
        Console.WriteLine(s.ToString());

        Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

        GC.Collect();
        Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

        GC.Collect();
        Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

        Console.Read();
    }
}

澳门新萄京官方网站 8;)

程序的输出为:

澳门新萄京官方网站 9

从这个输出,我们也可以验证代的概念,每次垃圾清理后,如果一个对象没有被清理,那么它的代就会提高。

public string Artist{get;set;}

  • 延迟对象初始化

强制垃圾回收

由于托管堆上的对象由垃圾管理器帮我们管理,所有我们不需要关心托管堆上对象的销毁以及内存空间的回收。

但是,有些特殊的情况下,我们可能需要通过GC.Collect()强制垃圾回收:

  1. 应用程序将要进入一段代码,这段代码不希望被可能的垃圾回收中断
  2. 应用程序刚刚分配非常多的对象,程序想在使用完这些对象后尽快的回收内存空间

在使用强制垃圾回收时,建议同时调用"GC.WaitForPendingFinalizers();",这样可以确定在程序继续执行之前,所有的可终结对象都必须执行必要的清除工作。但是要注意,GC.WaitForPendingFinalizers()会在回收过程中挂起调用的线程。

澳门新萄京官方网站 10;)

static void Main(string[] args)
{
    ……
    GC.Collect();
    GC.WaitForPendingFinalizers();
    ……
}

澳门新萄京官方网站 11;)

每一次垃圾回收过程都会损耗性能,所以要尽量避免通过GC.Collect()进行强制垃圾回收,除非遇到了真的需要强制垃圾回收的情况。

public string TrackName{get;set;}

     当一次实例化大量对象,会大大增加垃圾回收器的压力,但又不是所有的对象都立马需要使用,这时可以使用Lazy<>延迟对象实例化。

总结

本文介绍了.NET垃圾回收机制的基本工作过程,垃圾回收器通过遍历托管堆上的对象进行标记,然后清除所有的不可达对象;在托管堆上的对象都被设置了一个代,通过了代这个概念,垃圾回收的性能得到了优化。

public double TrackLength{get;set;}

  • 内存管理规则
    • 使用new关键字实例化类对象分配在托管堆上,然后就不用再管它了。
    • 如果托管堆没有足够的内存来分配所请求的对象,就会进行垃圾回收。
    • 重写Finalize()唯一的原因是,C#类使用了非托管资源。
    • 如果对象支持IDisposable则总是要对任何直接创建的对象调用Dispose(),应该认为如果类设计者选择支持Dispose方法,这个类型就需要执行清除工作。

}

 

class AllTracks

  • 强制垃圾回收

{

     垃圾回收 GC 类提供 GC.Collect 方法,您可以使用该方法让应用程序在一定程度上直接控制垃圾回收器。通常情况下,您应该避免调用任何回收方法,让垃圾回收器独立运行。在大多数情况下,垃圾回收器在确定执行回收的最佳时机方面更有优势。但是,在某些不常发生的情况下,强制回收可以提高应用程序的性能。当应用程序代码中某个确定的点上使用的内存量大量减少时,在这种情况下使用 GC.Collect 方法可能比较合适。例如,应用程序可能使用引用大量非托管资源的文档。当您的应用程序关闭该文档时,您完全知道已经不再需要文档曾使用的资源了。出于性能的原因,一次全部释放这些资源很有意义。

private Song[] allSongs=new Song[10000];

     在垃圾回收器执行回收之前,它会挂起当前正在执行的所有线程。如果不必要地多次调用 GC.Collect,这可能会造成性能问题。您还应该注意不要将调用 GC.Collect 的代码放置在程序中用户可以经常调用的点上。这可能会削弱垃圾回收器中优化引擎的作用,而垃圾回收器可以确定运行垃圾回收的最佳时间。

public AllTracks()

需要强制垃圾回收的场景

{

  • 应用程序将进入一段代码,后者不希望被可能的垃圾回收中断。
  • 应用程序刚刚分配非常多的对象,你想尽可能多地删除已获得的内存。

  • 对象的代

Console.WriteLine();

     CLR试图寻找不可访问对象时不会逐个检查托管堆上的每个对象,因为这样做会浪费大量的时间。为了优化这个过程,堆上的每个对象都被指定为属于某个代,代是垃圾回收器区分内存区域的逻辑视图,代的设计思路很简单,对象在堆上的存在时间约长就越应该保留。每次从0代开始检查释放内存空间,当空间不足时检查下一个代。

}

     对象在执行一次垃圾回收之后,会进入到下一代。也就是说如果在第一次执行垃圾回收时,存活下来的对象会进入第1代,如果在第2次垃圾回收之后该对象仍然没有被当作垃圾回收掉,它就会成为第2代对象,2代对象就是最老的对象不会在提升代数。

class MediaPlayer()

     当某代垃圾回收执行时,会同时执行更年轻代的垃圾回收。比如,当1代垃圾回收时会同时回收1代和0代的对象,当2代垃圾回收时会执行1代和0代的回收。

{

    • 第0代

public void Play(){};

     没有被标记为回收的新对象,通常对象是在0代就被回收的。

private AllTracks allsongs=new AllTracks();

    • 第1代

public AllTracks GetAllTracks()

     上次垃圾回收未被回收的对象,被标记为回收,但因为有足够的内存空间而未被删除的。1代对象是常驻内存对象和马上消亡对象之间的一个缓冲区。

{

    • 第2代

return allSongs;

     在一次以上的垃圾回收后仍然没有被回收的对象。

}

  • 大对象

}

     如果一个对象的大小超过85000byte,就认为这是一个大对象,这个数字是根据性能优化的经验得到的。当一个对象申请内存大小达到这个阀值,它就会被分配到大对象堆上。CLR垃圾回收器根据所占空间大小划分对象。大对象和小对象的处理方式有很大区别,比如内存碎片整理,在内存中移动大对象的成本是昂贵的。

}

     从代的角度看,大对象属于第2代对象,因为只有在2代回收时才会处理大对象。

main(){

     从物理存储角度看,对象分配在不同的托管堆上。一个内存分配请求就是将托管对象放到对应的托管堆上。如果对象的大小小于85000byte,它会被放置在SOH(小对象堆)上,否则会被放在LOH(大对象堆)上。   

MediaPlayer m=new MediaPlayer();//在这个时候,已经间接的创建10000个歌曲对象了

     当触发垃圾回收时,垃圾回收器会在小对象堆做碎片整理,将存活下来的对象移动到一起。而对于大对象堆,由于移动内存的开销很大,CLR团队选择只是清除它们,将回收掉的对象组成一个列表,以便满足下次有大对象申请使用内存,相邻的垃圾对象会被合并成一块空闲的内存块。

}

     需要时时留意的是在.Net中不会对大对象堆做碎片整理操作,因此如果你要分配大对象并不想他们被移动,你可以使用fixed语句。

若是将MediaPlayer修改为:

  • 大对象的回收
    • 在程序代码中调用GC.Collect方法时,如果在调用GC.Collect方法是传入GC.MaxGeneration参数时,会执行所有代对象的垃圾回收,包括大对象堆的垃圾回收。
    • CLR自动进行垃圾回收时,如果垃圾回收算法认为第2代回收是有成效的会触发第2代垃圾回收,例如操作系统内存不足时。
    • 大对象和第2代对象是一起回收的,如果大对象或者第2代对象占用空间超过其阀值时,就会触发第2代对象和大对象的回收。

class MediaPlayer()

{

  • 大对象对性能的影响

public void Play(){};

     如果是临时性的分配大对象,就需要很多的时间来运行垃圾回收,也就是说如果你持续的使用大对象然后又释放大对象对性能会有很大的负面影响。当回收大对象时又触发回收第2代对象,则对性能会产生更大的负面影响。

private Lazy<AllTracks> allsongs=new Lazy<AllTracks>();

 

public AllTracks GetAllTracks()

{

return allSongs.Value;

}

}

调用方式就要改为:

main(){

MediaPlayer m=new MediaPlayer();//在这个时候,未创建10000个歌曲对象了

AllTracks songs=m.GetAllTracks();//此时,歌曲对象才创建

}

本文由澳门新萄京官方网站发布于www.8455.com,转载请注明出处:澳门新萄京官方网站:net垃圾回收,对象生命周

关键词: