当前位置: 首页 > news >正文

C#性能优化基础:垃圾回收机制

  相信很多C#开发者都没有关注过内存问题,毕竟我们有垃圾自动回收机制,不用像C/C++那样,需要手动去释放。

  其实关于内存是自动还是手动回收释放,一直也是有争议的,像C/C++这样的开发者认为,内存这么珍贵,就应该让人去操作,怎么能让没有思维的机器去操作呢,而支持垃圾自动回收的开发者者认为,就是因为内存这么珍贵,才不能让人手动干预,机器更可靠!额,嗯,说的都对,都有道理,反正要么就是人靠谱,要么就是机器靠谱...

  好了,言归正传,在C#中,垃圾回收(Garbage Collection, GC)是一个自动内存管理机制,用于回收应用程序中不再被使用的对象的内存,因此,使用托管代码的开发人员无需编写执行内存管理任务的代码。C#中的垃圾回收是基于 .NET Framework 或 .NET Core/.NET 5+的公共语言运行时(CLR)的一部分。

  常用的垃圾分类方法有三种:引用计数法、标记删除法、分代回收法。

  引用计数法

    引用计数法,就是堆每个对象维护一个count字段,用来记录此对象被引用的次数1、当有新的引用指向时,引用计数+12、当该对象的引用减少时,引用计数 -13、如果对象的引用计数为0,该对象将被回收,空间将被释放

  一般的,引用技术法存在循环引用的问题,比如A引用B,B又引用A,那么A和B的引用计数都不为0,但是我们可以采用一些方法来解决这个问题。

  标记删除法

    标记清除是一种基于追踪回收的算法:1、第一步从跟对象出发,一直往后遍历,将那些可以被引用的对象标记为活动对象,那么剩下没有引用标记为非活动对象2、第二步就是将非活动对象删除回收了

  一般的,标记删除法存在性能问题,因为它需要扫描所有使用的内存。

  分代回收法

    分代回收法就是根据回收次数,将内存垃圾氛围3类:1、第0代(Generation 0):这是新分配的对象所在的代,由于新分配的对象很可能很快变为不可达(即不再被使用),因此第0代是垃圾回收最频繁检查的代。2、第1代(Generation 1):当第0代中的对象在一次垃圾回收后仍然存活时,它们会被提升到第1代。第1代中的对象在垃圾回收中的检查频率低于第0代,因为它们存活的时间更长,但是数量一般是最少的。3、第2代(Generation 2):类似地,如果第1代中的对象在另一次垃圾回收后仍然存活,它们会被提升到第2代。第2代是检查频率最低的代,因为其中的对象存活时间最长。

  C#的GC(Garbage Collection)

  C#的代码运行在CLR中,CRL负责资源申请、释放、异常监控等,内存属于资源的一种,所以,除非必须,C#代码不应该直接申请内存资源,而内存的释放交由GC(Garbage Collection)来控制,GC是分代回收器

  程序启动加载 CLR 时,GC 分配两个初始堆段:一个用于小型对象(小型对象堆或SOH),一个用于大型对象(大型对象堆或LOH)。

    大对象:对象的大小大于或等于 85,000 字节,运行时会将其分配到大型对象堆。

  对于大对象,它在第0代创建后,将会延续到第2代,放到LOH中,第 2 代垃圾回收未处理的对象仍是第 2 代对象,LOH 未处理的对象仍是 LOH 对象,所以LOH的回收是在第2代回收的时候进行的,第2代执行的垃圾回收被称为完整回收(Full GC),大对象堆(LOH)是我们开发者需要重点关注的

    GC回收的整个过程过程大概是这样:1、分配:申请内存,存放数据用于计算,此时数据可能在SOH或者LOH中2、晋升:根据引用打标记,应用程序中不再被使用的对象视为垃圾,按回收次数分为0、1、2代,数据在不同代中回收3、回收:清理数据,释放内存4、压缩:整理数据,退回多余的内存(LOH没有此过程)

  内存压缩

  这里解释一下内存压缩,就是GC的最后一步,我们内存是一个连续的块,但是在经历回收之后,它就变得断断续续的了,为了保证性能和资源的更好利用,GC可能会对内存做个压缩。

  对于SOH会压缩内存,这样所有的内存均可重新使用,借用官网的图说明一下:

    1、开始创建有四个对象obj0、obj1、obj2、obj3,这个时候它们在第0代2、现在obj1和obj3被释放了,那么它们的内存就空出来了,GC就会做个压缩,把obj2的位置就变到原来obj1开始的位置,同事它被提升到第1代3、如果又有obj4、obj5、obj6被提升到第1代,那么它们就会放在obj2后面,这样空间就被利用起来了4、如果obj2和obj5又被释放,那么又被压缩,obj4、obj6会向前移

  image

  而对于LOH由于压缩成本太高,因此一般是不会进行压缩(代码可以配置),在需要内存时,查看是否有可用的片段,没有则申请新的内存,但是用于申请的内存往往不一定刚好是空闲的长度,所以会流出很多Free的空间。借用官网的图说明一下:

    1、开始我们有obj0、obj1、obj2、obj3几个大对象,注意他们属于第2代2、当obj1、obj2被回收后,中间就会留下空闲片段(Free),GC一般不会去压缩3、当我们创建obj4需要新的空间时,会先看空闲片段是否满足需要,满足则使用,不满足就重新申请内存,但是哪怕满足,大概率也会有小的空闲片段

  image

  注意:GC执行垃圾回收时是阻塞操作,它会将所以线程挂起来,直至GC执行完成后继续执行。

  所以常说的GC会影响程序性能,其实就是只线程挂起来阻塞的时间,要缩短这个时间,就要尽可能少的产生垃圾,要尽可能快的执行回收,而且,如果频繁的执行GC,会发现CPU的使用率偏高,这可能对系统中的所有服务都回有一些影响。

  GC何时执行?

  GC的执行是个很复杂的过程,我们程序代码无法控制GC的执行,但是一般的,我们可以通过GC.Collect()方法来手动触发执行(虽然也不一定会执行),但是对于LOH,它往往在以下三种情况下执行:

    1、分配超出第0代或大型对象阈值2、调用 GC.Collect() 方法3、系统处于内存不足的状况

  

  总结

  在开发的过程中,为了避免GC影响性能,我们应该多注意一下。

    1、尽可能少的产生垃圾* 数据类型上,尽可能少的使用字符串、结构体之类的* 字符串使用上,注重使用字符串池(字符串暂存区),以内存的开销,对于碎片化的字符串,应采用StringBuilder优化* 注重复用,采用一些池技术优化,比如对象池2、尽可能快的执行回收,第2代回收频率是最低的,所以要尽量避免垃圾延续到第2代,特别是LOH,LOH存在的意义就是存放必须的大数据,所以非必须下不要使用LOH,LOH有着非常高的分配、回收成本。LOH中存放的数据往往是:大型集合、大字符串* 对于大型集合,比如大型的对象数据,我们需要在使用完成之后,将他们清空,以此取消他们之间的引用关系,从而可以让GC回收* 大字符串往往来自于三个地方:* 文件读取: var content = File.ReadAllText(filePath);* 格式化,如JSON格式: var json = value.ToJSON();* 数据库

 

  参考资料:

  https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap

 

http://www.sczhlp.com/news/139288/

相关文章:

  • 实验报告1
  • 网站可视化编辑怎么制作html文档
  • 黄村做网站建设坪山做网站公司
  • aspcms园林绿化工程网站源码加关键词的网站
  • 网站界面设计方案长沙企业网站开发哪家专业
  • 提供网站建设费用长春有什么好的网站制作公司
  • 什么APP可以做网站公司模板网站建设
  • 北京哪里可以申请企业网站域名官网网站商城建设合同
  • 合肥网站建设搜王道下拉广州企业网站推广策划方案
  • 想见你一个网站怎么做湖南株洲网站建设
  • 免费自助建站软件有哪些信息公开网站建设
  • 珠海门户网站建设价格网站开发摘要
  • 用jsp怎么做网站外贸汽车配件做那个网站
  • 网站建设平台选用如何创建小程序商店
  • 各大门户网站有哪些河南省建设厅资质公示
  • 【A】杂题选将
  • 有一个[1,5]的等概率随机函数fx(),在不改变fx()函数的情况下,利用fx()函数做出一个[1,7]的等概率随机函数。
  • WSL2 磁盘清理
  • 做公司网站哪家 上海网站建设为主题调研材料
  • 网站注册表单怎么做南山网站建设设计
  • 彩票网站建设一条龙采集电影做的网站
  • 温岭 网站建设商标注册名字查询系统官网
  • 金融网站建设公司怎么样学做网站
  • 做教育类的网站名wordpress多人博客
  • 网站 三合一joomla与wordpress哪个好
  • 怎么做html5网站营销型网站建设效果
  • 自己怎么做百度网站空间无烟锅网站规划与建设
  • 深圳做网站新科淘宝客网站模板下载
  • 关于OneBot的QQ机器人探索2
  • 好网站你知道的python网站开发好吗