当前位置: 澳门新濠3559 > 编程 > 正文

CLR就会调用垃圾回收器进行垃圾回收,垃圾回收

时间:2019-11-09 19:38来源:编程
在C#中,程序员无法直接在C#中删除一个托管对象,因为C#不提供这个功能,那么类的实例就需要通过CLR调用垃圾回收机制进行清除,回收内存。.NET垃圾回收器会压缩空的内存块来实现优

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

 

static void MakeCar()

  • GC 垃圾回收

{

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

Car mycar=new Car();

  • 将对象引用设置为空

}

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

在例子中,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<>,但凡被这个类所定义的数据在代码库实际使用它之前是不会被创建的,这样就可以使一些不常使用的大数据在实例化对象的时候不同时被创建,进而占用内存空间。例:

    • 根的类别
      • 全局对象的引用(C#中不允许,但CIL代码允许分配全局对象)
      • 静态对象和字段的引用
      • 应用程序代码库中的局部对象引用
      • 传递进一个方法的对象参数的引用
      • 等待被终结的对象的引用
      • 任何引用对象的CPU寄存器

class song{

public string Artist{get;set;}

  • 延迟对象初始化

public string TrackName{get;set;}

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

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();//此时,歌曲对象才创建

}

编辑:编程 本文来源:CLR就会调用垃圾回收器进行垃圾回收,垃圾回收

关键词:

  • 上一篇:没有了
  • 下一篇:没有了