簡體   English   中英

.NET CLR如何區分托管與非托管指針?

[英]How does the .NET CLR distinguish between Managed from Unmanaged Pointers?

所有東西最終都被嵌入到本機機器代碼中,因此最終,我們在.NET中有一個本機堆棧,只要它進行垃圾收集,GC就需要掃描對象指針。

現在,問題是:.NET垃圾收集器如何確定指向GC堆內對象的指針實際上是托管指針還是碰巧具有與有效地址對應的值的隨機整數?

顯然,如果它無法區分這兩者,那么就會出現內存泄漏,所以我想知道它是如何工作的。 或者 - 我敢說 - .NET有可能泄漏內存嗎? :o

正如其他人所指出的那樣,GC確切地知道堆棧和堆上每個塊的哪些字段是托管引用,因為GC和抖動知道所有內容的類型。

但是,你的觀點很好。 想象一個完全假設的世界, 在同一個過程中有兩種內存管理。 例如,假設你有一個用C ++編寫的名為“InterMothra Chro-Nagava-Sploranator”的完全假設的程序,該程序使用傳統的COM樣式引用計數內存管理,其中所有內容都只是指向進程內存的指針,並且通過調用釋放方法正確次數。 假設Sploranator假設有一種腳本語言JabbaScript,它維護一個垃圾收集的對象池。

當JabbaScript對象具有對非托管Sploranator對象的引用,並且同一Sploranator對象具有右引用時,會出現問題。 這是一個循環引用,不能被JabbaScript垃圾收集器破壞,因為它不知道Sploranator對象的內存布局。 因此存在內存泄漏的可能性。

解決此問題的一種方法是重寫Sploranator內存管理器,以便將其對象分配到托管GC池之外。

另一種方法是使用啟發式方法; GC可以專用處理器的一個線程來掃描所有內存,尋找恰好是指向其對象的指針的整數。 這聽起來很多,但它可以省略未提交的頁面,自己的托管堆中的頁面,已知僅包含代碼的頁面等等。 GC可以猜測,如果它認為一個對象可能已經死了,並且它無法在其控制之外的任何內存中找到任何指向該對象的指針,那么該對象幾乎肯定已經死了。

這種啟發式的缺點當然是錯誤的。 您可能有一個意外匹配指針的整數(雖然這在64位域中不太可能)。 這將延長對象的生命周期。 但誰在乎? 我們已經處於循環引用可以延長對象生命周期的情況。 我們試圖讓這種情況變得更好 ,而這種啟發式方法也是如此。 它不完美是無關緊要的; 它總比沒有好。

另一種可能是錯誤的方式是Sploranator可以對指針進行編碼 ,比如說,在存儲值時翻轉它的所有位,只在調用之前將其翻轉回來。 如果Sploranator對這種GC啟發式策略有積極的敵意 ,那么它就不起作用了。

這里概述的垃圾收集策略與任何產品的實際GC策略之間的相似性幾乎完全是巧合。 Eric關於假設的不存在產品的垃圾收集器的實施細節的思考僅用於娛樂目的。

垃圾收集器不需要推斷特定字節模式(無論是4字節還是8字節)是否是指針 - 它已經 知道了

在CLR中,所有內容都是強類型的,因此垃圾收集器知道字節是intlong ,對象引用,無類型指針等等。

內存中對象的布局是在編譯類型中定義的 - 存儲在程序集中的元數據給出了實例的每個成員的類型和位置。

堆棧幀的布局類似 - 編譯方法時JITter列出堆棧幀,並跟蹤存儲在哪里的數據類型。 (它由JITter完成,允許根據處理器的功能進行不同的優化)。

當垃圾收集器運行時,它可以訪問所有這些元數據,因此它永遠不需要猜測特定的位模式是否可能是引用。

Eric Lippert的博客是一個了解更多信息的好地方 - 參考文獻不是地址可以開始。

請記住,所有托管內存都由CLR管理。 任何實際的托管引用都是由CLR創建的。 它知道它創造了什么,不知道它是什么。

如果你真的覺得你必須知道實現的細節,那么你應該通過C# by Jeffrey Richter閱讀CLR 答案並不簡單 - 它的引用比SO上的答案要多一些。

那么當JITing代碼時,編譯器知道它在哪些地方放置了對象的引用。 每當你在一個包含引用的方法中使用一個字段時,它就知道在那個地方有一個引用。 JIT代碼時也可以保留此信息。

現在,參考指向該對象。 每個對象都有一個指向其類的指針(.GetType() - 方法)。 基本上GC現在可以使用指針,跟隨它,讀取對象的類型。 該類型告訴您是否有其他字段包含對其他對象的引用。 這樣GC就可以遍歷整個堆棧和堆棧。

當然這有點過於簡化,但基本原理。 最后是一個實現細節。 當然,還有其他方法和各種技巧可以有效地完成這項工作。

注釋后更新:堆棧上的指針指向堆上的對象。 每個對象都有一個標題,它還包含指向其type-in​​fo的指針。 因此,您可以取消引用堆棧上的指針,取消引用指向object-info的指針,以找出它是什么類型的對象。

參閱Microsoft .NET Framework中的垃圾收集:自動內存管理
(某些技術細節可能有點過時,但所描述的結構是有效的。)

文章中的一些簡要說明......

初始化進程時,運行時會保留一個連續的地址空間區域,該區域最初沒有為其分配存儲空間。 此地址空間區域是托管堆。 堆還維護一個指針,我稱之為NextObjPtr。 此指針指示在堆中分配下一個對象的位置。 最初,NextObjPtr被設置為保留地址空間區域的基地址。

...

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

...

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

關於這個問題......

...或一個碰巧有一個與有效地址對應的值的隨機整數?....內存泄漏?

如果無法訪問該對象,則無論如何GC都會將其銷毀。

根據“CLR via C#”一書,運行時通過檢查“方法的內部表”確切地知道在哪里找到引用/指針。 這個內部表在微軟實現中的含義是未知的,但它可以確定地識別堆棧上的調用幀,局部變量,甚至寄存器為每個EIP地址保存的值。

單聲道實現使用保守掃描,這意味着將堆棧上的每個值都視為潛在指針。 這不僅會轉換為內存泄漏,而且(因為它無法更新這些值)由此標識的對象被視為固定(由GC壓縮程序無法移動)並導致內存碎片。

現在mono可以選擇使用GCMaps的“精確堆棧標記”。 你可以在這里閱讀更多內容http://www.mono-project.com/Generational_GC#Precise_Stack_Marking

請注意,此實現不准確,因為它是MS,因為它繼續保守地處理當前幀。

在.NET中創建新的引用類型對象時,您將自動使用CLR及其GC“注冊”它。 無法將隨機值類型注入此過程。 換一種說法:

CLR不會維護一些與值類型混合的大型,無組織的指針堆。 它只跟蹤CLR創建的對象(無論如何都是為了垃圾收集目的。)任何值類型都將在堆棧上短暫存在或者是類實例的成員。 GC沒有混淆的可能性。

引用有標題,所以它不僅僅是一個隨機整數。

暫無
暫無

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

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