高性能-GC

帶着問題去思考!你們好函數

相對.NET 來講。CLR去處理了,C,C++這些就須要手動去垃圾回收。性能

  GC大部分容易察覺的性能問題。其實不少問題實際是哪一個都是因爲對垃圾回收器的行爲和預期結果理解有誤。在,NET環境中,你須要更多的關注內存的性能,那麼接下里咱們主要是講內存性能問題。spa

  GC實際上會調總體提升內存堆[1]的性能,由於他能高效的完成內存分配和碎片整理工做。操作系統

  在Windows的本機代碼模式下,內存堆維護着一張空閒內存塊的列表,用於內存的分配,儘可能用低碎片化的內存堆,由於長時間運行,還要考慮內存碎片問題,內存佔用率會持續增加。因此本機代碼程序用大量代碼實現了本身的內存分配機制,把默認的malloc函數給替換掉線程

  在.NET環境中,內存分配的工做量會很小,由於內存老是整段分配的。因此不會比內存的擴大,減少和比較增長多少開銷,不存在遍歷空閒內存列表,幾乎不會出現內存碎片,GC內存堆的效率還會更高的,由於連續分配的多個對象每每在內存堆中也是連續存放,提升就近訪問的可能性。指針

  在默認的內存分配流程中,有一小段代碼會先檢查目標對象的大小,看看內存分配緩衝區中所剩的內存夠不夠,若是內存分配緩衝區已耗盡,就會交由GC分配程序來檢索足以容納目標對象的空閒內存。而後一個新的分配緩衝區就會被保留下來。code

  接下里看下內存分配的過程對象

  class MyObject
        {
            int x;
            int y;
            int z;
        }
        static void Main(string[] args)
        {
            var x = new MyObject();
        }

瞭解彙編語言的都知道這一流程,blog

 

 

 MOV指令是數據傳送指令,其餘的你們能夠自行去查約。大體是說,把類的方法表指針拷貝到ecx(計數暫存器)中,做爲new ()的參數,調用new,把返回值(對象的地址)拷貝到寄存器中。這裏你們大概瞭解一下就能夠了隊列

基本運做方式

 

在託管進程中存在兩種內存堆(本機堆和託管堆),這裏咱們說下託管堆,本機堆大你們能夠自行了解下,

CLR在託管堆(Managed Heap)上爲全部的.NET託管對象分配內存,也稱之爲GC堆,由於其中的對象都要受到垃圾回收機制的控制。

  託管堆又分爲兩種,小對象堆和大對象堆(LOH),都擁有本身的內存段(Segment[ˈseɡmənt]).內存段的大小視配置和硬件環境而定。

小對象堆

  小對象堆有什麼?它又是怎麼變化的?

  小對象堆的內存堆分爲3代,0,1,2代。第0代和第1代老是位於同一個內存段中,第2代可能跨越多個內存段,LOH也能夠跨越多個內存段。包含第0代和第1代堆的內存段被稱爲暫時段(Ephemeral [ɪˈfemərəl] Segment ˈseɡmənt]

 

   小對象堆中分配內存的對象生存期。若是 對象小於85000字節,CLR會把它分配在小對象堆中的第0代,一般緊挨當前已用內存空間日後分配,若是擴大內存堆時超越了內存段的邊界,則就會觸發垃圾回收過程。

  對象老是誕生於第0代內存堆中,只要對象保持存活,每當發生垃圾回收時,GC都會把他提高一代。第0代和第1代內存堆的垃圾回收有時候被稱爲瞬時回收(Ephemeral Collection)

  在垃圾回收的時候,可能會進行碎片整理(Compaction),也及時GC把對象物理遷移到新的位置中去,以便讓內存段中的空閒空間可以連續起來使用。若是爲發生碎片整理,那就只須要從新調整各塊內存的邊界。如下是經歷幾回未作碎片整理的垃圾回收以後,內存堆的分佈可能以下

對象的位置沒有移動,可是各代的內存堆的邊界發生了變化。

  若是對象到達第2代內存堆,它就會一直留在哪裏直至終結。但不表明第2代內存堆只會一直變大, 若是第2代內存堆中的對象都終結了,整個內存段有沒有存活[2]的對象,垃圾回收器會把整個內存段交換給操做系統,或者做爲其餘幾代內存堆的附加段,在進行徹底垃圾回收(Full Garbge Collection)時,就會可能發生第2代內存堆的回收。

[2]:存活:GC能經過任一已知的GC跟對象(Root),沿着層層引用訪問到某個對象,那麼它是存活的。GC的根對象能夠是程序中的靜態變量,或者某個線程的堆棧被正在運行的方法佔用(局部變量)或者GC句柄(好比固定對象的句柄,Pinned Handle),或是終結器隊列(Finalizer Queue),有些對象可能沒有受GC根對象的引用,但若是位於第2代內存堆中,那麼第0代回收是不會清理這些對象的。只有徹底垃圾回收纔會被清理。

   若是第0代堆即將佔滿一個內存段,垃圾回收也沒法經過碎片整理獲取足夠空閒內存,那麼GC會分配一個新的內存段。如圖:

  

 

 若是第2代堆繼續變大,就可能會跨越多個內存段。LOH也是。可是不管存在多少內存段,第0代和第1代老是位於同一個內存段中。之後咱們找出內存堆中有哪些對象存活時。這些會用到。

LOH

LOH,大於85000字節的對象將自動在LOH中分配內存。沒有代的概念,垃圾回收期間也不會自動進行碎片化整理,可是能夠人爲的碎片整理。

垃圾回收的時候會形成什麼影響呢?

  垃圾回收是針對某一代及如下幾代內存堆進行的。若是回收1代,就會回收0代。若是發生了第0代或第1代垃圾回收,程序在回收期間就會暫停運行,第二代垃圾回收,有部分回收是在後臺線程運行進行的。

垃圾回收的4個階段

  • 掛起(Suspension)---在垃圾回收發生以前,全部託管線程都被強行停止
  • 標記(Mark)--從GC根對象開始,垃圾回收器沿着全部對象引用進行遍歷並把所見對象記錄下來
  • 碎片整理(Compact)--將對象從新緊挨着存放並更新全部引用,以便減小內存碎片,在小對象堆中,碎片整理會按需進行,沒法控制。在LOH中,碎片整理不會自動進行,能夠必要時通知垃圾回收器來上一次。
  • 恢復(Resume)--託管線程恢復運行

在標記階段,不須要遍歷內存堆中的全部對象,只要訪問那些須要回收的部分即。好比第0代回收只涉及到第0代內存堆中的對象,第1代回收將會標記第0代和第1代內存中的對象。第2代和徹底回收,須要遍歷內存堆中的全部存活對象,這一開銷很大。

  1:垃圾回收過程的耗時幾乎徹底取決於所涉及的「代」內存堆中的對象數量,而不是你分配到的對象數量,你分配1棵包含100萬個對象的樹,只要在下次垃圾回收以前把根對象的引用解除。就不會增長垃圾回收的耗時

  2:只要已分配的內存超過某個內部閥值,就會發生這個代垃圾回收,這個閥值是持續變化的。GC會進行調整。

計數器

相關文章
相關標籤/搜索