高性能-GC2

帶着問題去思考!大家好

上次我們講到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代對象的引用。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章