随笔 - 36  文章 - 0 评论 - 2 trackbacks - 0

带着问题去思考!大家好

上次我们讲到GC的一些基础知识,感谢评论的大佬给我指点。

配置参数

  关于配置垃圾回收器的方法不是很多,所以建议不要随意去动,垃圾回收器的配置以及调优,很大程度上由硬件配置,可用资源和程序的行为决定。屈指可数的几个参数也是用于控制很高层的行为,主要取决于程序的类型,

工作站模式还是服务器模式?

  在垃圾回收默认采用的工作站模式。工作站模式,所有的GC都运行于触发垃圾回收的线程中,优先级(Priority praɪˈɔːrəti)也相同。对于单机处理器的计算机而言,工作站模式是唯一选择,配置其他参数也是无效的

  服务器模式下,GC会为每个逻辑处理器或处理器核心创建各自专用的线程。这些线程的优先级最高的,但在需要进行 垃圾回收之前会一直保持挂起状态。垃圾回收完成后,这些线程会再次进如休眠状态。

  此外,CLR还会为每个处理器创建各自肚子的内存堆,每个处理器堆都包含1个小对象堆和1个LOH。从应用程序角度来看,就只有一个逻辑内存堆。你的代码不清楚对象属于哪一个堆,对象引用会在所有堆之间交叉进行。

  多个内存堆的存在会带来一写好处。

  1:垃圾回收可以并行进行,每个垃圾回收线程负责回收一个内存堆,这可以让垃圾回收的速度明显快鱼工作站模式

  2:某些情况下,内存分配的速度也会更快一些,特别是对LOH而言,因为会在所有内存堆中同时进行份分配。

具体配置

  

<gcServer enabled="true">

在runtime节点下。

说到底到底用哪个好呢?如果应用程序运行于专为你准备的多处理器主机上,那么选择服务器模式。这样大部分情况,都能让垃圾回收占用的时间降至最低。

如果需要多个托管进行共用一台主机,那么服务器模式的垃圾回收会创建多个高优先级的线程。如果多个应用程序都这么设置,那线程调度就会相互带来负面影响,如果你确定想让同一台主机的多个应用程序使用服务器模式的垃圾回收,还有一种做法,就是让存在竞争关系的应用程序都集中在指定的几个处理器上运行,这样CLR只会为这些处理器创建自己的内存堆。

后台垃圾回收

首先BackgroundGC只会影响第2代内存堆的垃圾回收行为,第0代和第1代的垃圾回收仍会采用前台的垃圾回收,也就是会阻塞所有应用程序的线程。后台垃圾回收由一个专用的第2代堆垃圾回收线程完成,

如果关闭,但不建议

<gcConcurrent enabled="false"/>

 

低延迟模式

如果一段时间内确保较高的性能,可以通知GC不要执行开销很大的第2代垃圾回收。请根据其他参数把GCSettings.LatencyModel属性赋为以下值之一。

  LowLatency---仅适用与工作站模式GC,禁止第2代垃圾回收。

  SustainedLowLatency--适用与工作站和服务器模式的GC,禁止第2代完全垃圾回收,但允许第2代后台垃圾回收,必须启用后台垃圾回收。

因为不会再进行碎片整理了,所以这两种参数都会显著增加托管堆的大小,如果你的进程需要大量内存,就应该避免使用这种延迟模式。在即将进入低延迟模式前,最好是能强制执行一次完全垃圾回收,这通过调用GC.Collect即可完成,当代离开低延迟模式后,马上再做一次完全垃圾回收,

以下条件都满足时,才能开启低延迟模式

  • 完全垃圾回收的持续时间过长,是程序正常运行时绝对不能接受的
  • 应用程序的内存占用量远低于可用内存数。
  • 无论是关闭低延迟模式期间,程序重启,还是手动执行完全垃圾回收期间,应用程序都可以保持存活状态。

如何减少内存的分配量

  要想减少内存的分配数量,请严格审查没一个对象。

  • 是否真的需要这个对象?
  • 对象中有没有什么成员可以摒弃
  • 数组能否减小一些?
  • 基元类型(Primitive)是否减少体积(比如Int64换成Int32)?
  • 有些对象是否很少用到,仅必要时在进行分配?
  • 优先类能否转成“结构”(Struct)?这样就能存放在堆栈中,或者是成为其他对象的成员
  • 分配的内存很多,是否只用了一小部分?
  • 是否用其他途径获取数据?

首要规则

  其实我们都已经知道,针对垃圾回收器,存在一条基本的高性能编码规则。其实垃圾回收器明显就是按照这条规则进行设计。

只对第0内存堆中的对象进行垃圾回收

  简而言之,对象的生存期尽可能短暂,这样垃圾回收器根本就不会去触及它们,或者做不到转瞬即逝,就让对象尽快提升到 第2代内存堆并永远留在哪里,再也不会被回收。这意味着需要一直保存一个堆长久存活对象的引用,通常这也意味要把可重用的对象进行池化(Pooling),特别是LOH中的所有对象。

缩短对象的生命期

  • 对象的作用域越小在垃圾回收时就越没有机会被提升到下一代。
  • 一般来说,对象在使用前不应该被分配内存。除非创建对象的开销太大。需要提早创建才不至于影响到其他操作的执行,
  • 另外在使用对象时,确保对象尽快地离开作用域。对于局部变量而言,可能是最后一次局部使用之后,甚至可以在方法结束之前。
  • 如果你的代码要对某个对象进行多次操作,请尽量缩短第一次和最后一次使用的间隔,这样GC能尽早的回收这个对象了,
  • 如果某个对象的引用是一个长时间存活对象的成员,有时你需要把这个引用显示地设置为null,这也许会稍微增加一点代码的复杂度,因为你需要随时准备多检查一下null值,

减少对象树的深度

  我们都知道,GC将会沿着对象引用遍历,在服务器模式GC中,一次会有多个线程同时遍历,你肯定希望尽可能的利用这种并发机制,但如果有某个线程陷入一条很长的嵌套对象链中,那么整个垃圾回收过程就得等整个线程完成工作后才会结束。

减少对象间的引用

如果对象引用了很多其他对象,垃圾收集器对其遍历时就要耗费更多的时间。如果垃圾回收引用的暂时时间较长,往往意味着有大型,复杂的对象间引用关系存在,如果难以确定对象所有的被引用关系,还有一个风险就是何难预测对象的生存期,减少对象引用的复杂度,不仅对提高代码质量有利,而且可以让代码调试和修正性能问题变得更加容易,还需要注意,不同代的内存堆之间的对象引用可能会导致垃圾回收器的低效运行,特别是从老对象中引用新对象的情况,比如第2代内存堆中有个对象包含了对第0代内存对对象的引用,这样每次第0代垃圾回收,总有一部分第2代内存堆中的对象不得不被遍历到。以便确认它们是否还持有堆第0代对象的引用。

 

posted on 2020-03-25 23:36  梦一回  阅读(...)  评论(...编辑  收藏