《.NET 性能优化》—第四章 垃圾回收

通过书本的阅读,看得我是云里雾里,不过大致把该章节过了一遍,仅仅记住了几个单词,“代”,大对象堆,固定。具体的描述也不是很明白,所以先从网络找一些资源,挑出能看懂的做一些笔记,以后待稍有能力,再做回看。

最具权威的MSDN: .NET 中的内存管理和垃圾回收
用到的类库:
System.GC
System.GCCollectionMode
System.GCNotificationStatus
System.Runtime.GCLatencyMode
System.Runtime.GCSettings
GCSettings.LargeObjectHeapCompactionMode
Object.Finalize
System.IDisposable

GC 是什么

GC如其名,就是垃圾收集,当然这里仅就内存而言。Garbage Collector(垃圾收集器,在不至于混淆的情况下也成为GC)以应用程序的root为基础,遍历应用程序在Heap上动态分配的所有对象[2],通过识别它们是否被引用来确定哪些对象是已经死亡的、哪些仍需要被使用。已经不再被应用程序的root或者别的对象所引用的对象就是已经死亡的对象,即所谓的垃圾,需要被回收。这就是GC工作的原理。为了实现这个原理,GC有多种算法。比较常见的算法有Reference Counting,Mark Sweep,Copy Collection等等。目前主流的虚拟系统.NET CLR,Java VM和Rotor都是采用的Mark Sweep算法。

为什么要使用GC呢?

也可以说是为什么要使用内存自动管理?有下面的几个原因:
  1、提高了软件开发的抽象度;
  2、程序员可以将精力集中在实际的问题上而不用分心来管理内存的问题;
  3、可以使模块的接口更加的清晰,减小模块间的偶合;
  4、大大减少了内存人为管理不当所带来的Bug;
  5、使内存管理更加高效。

总的说来就是GC可以使程序员可以从复杂的内存问题中摆脱出来,从而提高了软件开发的速度、质量和安全性。

参考:https://www.cnblogs.com/nele/p/5673215.html

“代”

.NET GC使用"代"(generations)的概念来优化性能。代帮助GC更迅速的识别那些最可能成为垃圾的对象。在上次执行完垃圾回收后新创建的对象为第0代对象。经历了一次GC周期的对象为第1代对象。经历了两次或更多的GC周期的对象为第2代对象。代的作用是为了区分局部变量和需要在应用程序生存周期中一直存活的对象。大部分第0代对象是局部变量。成员变量和全局变量很快变成第1代对象并最终成为第2代对象。GC是一个非常复杂的过程,调用GC.Collect,一旦发生调用都会挂起所有工作线程,就会去依次遍历3代内存根,然后对未有引用的内存块标记,然后调用终结器,最后将有引用的内存块提升一代。
参考:https://www.cnblogs.com/visionwang/p/3371108.html

# .NET中GC对应用的性能影响

知道GC何时会发生,但是GC执行的过程如何?执行完的结果如何?执行的结果是否是我们所期望的?这些问题我们从哪里去找答案?性能计数器这时就起到作用了。我们大致分析一下与GC相关的几个计数器(Counter)的作用:

% Time in GC

这个值是说从上一次GC结束到当前这次GC所经历的时间的百分比。比如上次GC结束时经历了100个循环,当前的GC经历了50个循环,这个计数器的值就是50/100=50%。看性能计数器来推测究竟是什么问题,主要有两类情况,第一类需要看计数器到变化趋势,第二类需要看的是计数器到值。这里对待第2类情况引入一个“健康值”的概念。通常来说如果值大于50%我们就应该去检查一下托管堆的问题了,如果这个值在20%以下,一般来说是没有必要去优化程序的。如果系统负载较高或内存压力较大,都是有可能导致该值升高的。

Large Object Heap Size

这个值记录的是LOH的大小。

Gen X Collections

这里X的值为0,1和2。该计数器显示了从这个process开始运行后各个代被回收的次数。从GC的实现上来讲,其实是没有针对Gen0进行回收的操作。GC对代操作最大的一个特点是对于高的代进行回收操作也会对比它低的所有的代进行回收操作。
GC是其软怕硬的“软骨头”,对于小对象,它把最容易摆平的对象在较低的代上就解决之,摆不平的就会将它发配到高一级的代上,直到Gen2,GC才变得凶猛彪悍——当然程序就要为它的强势付出较高的性能损失的代价。GC什么时候最弱势?就是面对对大对象的时候了,GC对于大对象变得很恐惧,不对该堆压缩也不做提升,就只把它们列到一份黑名单里伺机针对其中的部分下黑手。但是GC的这个特点带给我们的却是大多数情况下性能的提升。Gen2一出手,就会带着LOH的GC一起动手,这个特点有时就成了万恶的开端了。
针对这个特点,我们可以清晰的看出Gen0和Gen1运行很频繁也不会影响太多的性能,但是Gen2的频繁就让我们感觉比较明显了,如果Gen2运行的很频繁,那程序就如坐针毡了。通常来说,Gen0、Gen1和Gen2的回收次数的比值在100:10:1的比率上是不错的。
我们的在托管堆上的小对象往往有两种情况,一种是这个对象在Gen0回收时就被处理掉了,我们这里况且称之为“夭折”(die young),还有一种开起来很顽强但是一到Gen2立刻就挂了的,我们称之为“中年危机”(mid-life crisis)。对于前者,是个好事情,因为Gen0对性能的影响几乎可以忽略不计。但是对于频繁的Gen2的回收可能出现的一个情况就是LOH 的回收也伴随着Gen2的回收而执行——即使LOH还有富余的空间可以分配,但也跟着遭殃了。
另外一种情况就是我们看到在Gen2回收之后,其大小并没有明显的变化,这表明回收操作基本都发生在针对LOH的处理上了。对于LOH,里面的对象其实也不全是大于85k的,还有一些.net运行时在里面创建的对象。
所以,如果我们看到GC消耗了不少时间,但是其分配速率却不高的话,最大的可能就是有很多对象在不断的从Gen0提升到Gen2。这种场景往往可以通过下面这个计数器的值得到确认。

参考:# .NET中GC对应用的性能影响

.常见面试题目-GC与内存管理

个人觉得这篇文章很使用。

  1. 简述一下一个引用对象的生命周期?

  2. 创建下面对象实例,需要申请多少内存空间?
    public class User
    {
    public int Age { get; set; }
    public string Name { get; set; }

    public string _Name = "123" + "abc";
    public List<string> _Names;
    }

  3. 什么是垃圾?

  4. GC是什么,简述一下GC的工作方式?

  5. GC进行垃圾回收时的主要流程是?

  6. GC在哪些情况下回进行回收工作?

  7. using() 语法是如何确保对象资源被释放的?如果内部出现异常依然会释放资源吗?

  8. 解释一下C#里的析构函数?为什么有些编程建议里不推荐使用析构函数呢?

  9. Finalize() 和 Dispose() 之间的区别?

  10. Dispose和Finalize方法在何时被调用?

  11. .NET中的托管堆中是否可能出现内存泄露的现象?

  12. 在托管堆上创建新对象有哪几种常见方式?
    可参考# .NET面试题解析(06)-GC与内存管理寻求答案

GC是如何工作的

GC的工作流程主要分为如下几个步骤:

标记(Mark) → 计划(Plan) → 清理(Sweep) → 引用更新(Relocate) → 压缩(Compact)


2789632-3c499324515f168f.png

参考:# .NET 之 垃圾回收机制GC

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值