簡體   English   中英

壓縮后GC如何更新參考

[英]How does the GC update references after compaction occurs

.NET垃圾收集器收集對象(回收其內存),並執行內存壓縮(以將內存碎片降到最低)。

我想知道,由於應用程序可能有許多對對象的引用,當對象的地址由於由GC壓縮而發生變化時,GC(或CLR)如何管理這些對對象的引用。

這個概念很簡單,垃圾收集器僅更新任何對象引用並將它們重新指向已移動的對象。

實現有點棘手,本機代碼和托管代碼之間沒有真正的區別,它們都是機器代碼。 而且對象引用沒有什么特別的,它只是運行時的指針。 所需要的是收集器找到這些指針並將其識別為引用托管對象的一種可靠方式。 不僅要在壓縮時移動指向的對象時更新它們,而且還要識別可確保對象不會過早收集的實時引用。

對於存儲在GC堆中存儲的類對象中的任何對象引用,這都很簡單,CLR知道對象的布局以及哪些字段存儲了指針。 對於存儲在堆棧或cpu寄存器中的對象引用,並不是那么簡單。 像局部變量和方法參數一樣。

執行托管代碼與本地代碼的不同之處在於,CLR可以可靠地迭代托管代碼擁有的堆棧幀。 通過限制用於設置堆棧框架的代碼種類來完成。 這在本機代碼中通常是不可能的,“忽略幀指針”優化選項特別討厭。

首先通過棧幀遍歷可以找到存儲在棧中的對象引用。 並且讓它知道線程當前正在執行托管代碼,因此也應該檢查cpu寄存器中的引用。 從托管代碼到本機代碼的轉換涉及在收集器可以識別的堆棧上編寫特殊的“ cookie”。 因此,它知道不應檢查任何后續堆棧幀,因為它們將包含從未引用托管對象的隨機指針值。

啟用非托管代碼調試時,您可以在調試器中看到這一點。 查看“調用堆棧”窗口,並注意[本地到托管過渡]和[托管到本地過渡]批注。 那就是調試器識別那些cookie。 這也很重要,因為它需要知道“本地”窗口是否可以顯示任何有意義的內容。 堆棧遍歷也顯示在框架中,請注意StackTrace和StackFrame類。 對於沙箱而言,這很重要,代碼訪問安全性(CAS)執行堆棧遍歷。

為簡單起見,我將假設一個世界停滯的GC,其中沒有固定對象,每個對象在每個GC周期都被掃描並重新放置,並且沒有任何目標與任何源重疊。 實際上,.NET GC稍微復雜一些,但這應該可以很好地了解事物的工作方式。

每次檢查參考文獻時,都有以下三種可能性:

  1. 是空的 在這種情況下,無需采取任何措施。

  2. 它標識一個標頭說它是重定位標記(以下描述的特殊類型的對象)以外的對象的對象。 在這種情況下,將對象移動到新位置,並用包含新位置的三字重定位標記替換原始對象,該對象的位置包含對當前對象的剛觀察到的引用 ,以及其中的偏移量該對象。 然后開始掃描新對象(由於它只是記錄其地址,因此系統可以暫時忽略正在掃描的對象)。

  3. 它標識一個標頭說它是重定位標記的對象。 在這種情況下,請更新正在掃描的參考以反映新地址。

系統完成對當前對象的掃描后,可以在開始掃描當前對象之前查看它的舊位置以查明它在做什么。

重定位對象后,其前三個單詞的前內容將在新位置可用,而在舊位置不再需要。 由於對象的偏移量始終是四個的倍數,並且每個對象的最大限制為2GB,因此僅需要所有可能的32位值的一小部分即可保存所有可能的偏移量。 如果對象標頭中的至少一個單詞具有至少2 ^ 29個值,則除了對象重定位標記外,它永遠無法容納其他任何內容,並且為每個對象分配了至少十二個字節,則對象掃描可以處理任何樹的深度,無需在不再需要其內容的對象的舊副本所占用的空間之外進行任何與深度相關的存儲。

垃圾收集

每個應用程序都有一組根。 根標識存儲位置,這些存儲位置引用托管堆上的對象或設置為null的對象。 例如,應用程序中的所有全局和靜態對象指針都被視為應用程序根目錄的一部分。 另外,線程堆棧上的任何局部變量/參數對象指針都被視為應用程序根目錄的一部分。 最后,任何包含指向托管堆中對象的指針的CPU寄存器也被視為應用程序根目錄的一部分。 活動根的列表由即時(JIT)編譯器和公共語言運行時維護,並且可以由垃圾收集器的算法訪問。

當垃圾收集器開始運行時,它假定堆中的所有對象都是垃圾。 換句話說,它假定應用程序的根均未引用堆中的任何對象。 現在,垃圾收集器開始遍歷根目錄,並為從根目錄可訪問的所有對象建立圖形。 例如,垃圾收集器可以定位一個指向堆中對象的全局變量。

圖的這一部分完成后,垃圾收集器將檢查下一個根並再次遍歷對象。 當垃圾收集器從一個對象移動到另一個對象時,如果它試圖將一個對象添加到先前添加的圖形中,則垃圾收集器可以停止沿該路徑移動。 這有兩個目的。 首先,它不會多次遍歷一組對象,因此可顯着提高性能。 其次,如果您有任何循環鏈接的對象列表,它可以防止無限循環。

一旦檢查完所有的根,垃圾收集器的圖形就會包含從應用程序的根以某種方式可以訪問的所有對象的集合。 應用程序無法訪問該圖中未包含的任何對象,因此將其視為垃圾。 現在,垃圾收集器以線性方式遍歷堆,查找垃圾對象的連續塊(​​現已視為可用空間)。 然后,垃圾收集器將非垃圾對象向下移動到內存中(使用多年以來已知的標准memcpy函數),從而消除了堆中的所有間隙。 當然,在內存中移動對象會使指向該對象的所有指針無效。 因此,垃圾收集器必須修改應用程序的根,以便指針指向對象的新位置。 另外,如果任何對象包含指向另一個對象的指針,則垃圾收集器還負責更正這些指針。

C#固定語句

固定語句設置指向托管變量的指針,並在語句執行期間“固定”該變量。 如果沒有固定的變量,則指向可移動托管變量的指針將很少使用,因為垃圾回收可能會意外地重新定位變量。 C#編譯器僅允許您在固定語句中將指針分配給托管變量。

垃圾收集:Microsoft .NET Framework中的自動內存管理

固定語句(C#參考)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM