重學c#系列——c# 託管和非託管資源(三)

團隊buff工具人發表於2020-07-14

前言

c# 託管和非託管比較重要,因為這涉及到資源的釋放。

現在只要在計算機上執行的,無論玩出什麼花來,整個什麼概念,逃不過輸入資料修改資料輸出資料(計算機本質),這裡面有個資料的輸入,那麼我們的記憶體有限啊,這裡面就牽扯到資料釋放。

看下c# 的垃圾回收是怎麼樣的。

瞭解垃圾回收之前首先要了解資料,瞭解資料需要了解資料型別啊,資料型別分為值型別還有引用型別。

windows 使用一個虛擬定址系統,該系統把程式可用的記憶體地址對映到硬體記憶體中的實際地址上,這些任務完全由windows 在後臺管理。我們的程式執行在作業系統上,那麼我們作為程式設計師關係的就是這個虛擬定址系統。

這東西有什麼用呢?

比如32位系統中,每個程式所佔用的最多4G(4G這樣來的,2^32,4個位元組),那麼這個程式如果進行管理的這4G,它不需要知道在硬體地址是多少。

比如這個程式申請了1k記憶體,那麼這個程式管理的實際是從0到1k的虛擬記憶體,而不需要知道這個硬體實體記憶體地址是多少,有一個可以直接證明的就是我們寫c++輸出指標的時候,發現指標輸出1千多,

你覺得可能是實體記憶體地址的1千多嗎?默默的開啟資源管理看看現在佔用多少記憶體。

預設情況下,32 位計算機上的每個程式都具有 2 GB 的使用者模式虛擬地址空間。這裡解釋一下,每個程式2個G是虛擬地址,就是在這個程式維護一個2G的虛擬地址,並不是實際佔有2G的硬體記憶體地址。

盜一張圖:

虛擬地址有三種狀態:

狀態 描述
Free 該記憶體塊沒有引用關係,可用於分配。
保留 記憶體塊可供你使用,並且不能用於任何其他分配請求。 但是,在該記憶體塊提交之前,你無法將資料儲存到其中。
已提交 記憶體塊已指派給物理儲存。

那麼這個虛擬記憶體上又分了堆和棧,棧上儲存值型別,堆上儲存引用型別。

他們的儲存方式不一樣。

下面是棧:

棧是這樣子的先用高位後用低為,比如申請80000,先用的就是80000 直到為0為止。

{
 int a=10;
 double b=100.0;
}

如上圖,80000用完了,這時候棧指標指向80000。

現在int a了,int是4個位元組,這時候棧指標減4,到79996這個位置。

然後是double,double 為8個位元組,這時候棧指標減8,以此類推。

然後如果變數超出作用域,那麼這個時候就會被垃圾回收,棧指標增加8,然後增加4。(記得棧指標增加的時候[垃圾回收]並不會去把已經使用的地址重置為0,只有型別申明的時候才重置為0,然後再賦值)

下面是引用型別:

堆是這樣子的,已用的記憶體地址小,空閒的記憶體地址大。

舉個例子:

{
  student a;
  a=new student;
}

首先執行student a,這個時候儲存的是引用地址,也就是4個位元組,存放在棧上。

然後執行a=new student(),首先假設student使用64個位元組,那麼在堆上就申請連續的64個位元組,然後把首地址傳遞賦值給a。

上面非常大的程度簡化了程式的記憶體分配,引用型別垃圾回收的一個簡化版就是——當一個引用變數超出作用域的時候,它會從棧中刪除,但是引用物件的資料儲存在堆中,只有沒有任何一個引用變數指向引用物件的時候那麼這個引用物件才會回收。

好的,知道了資料儲存是怎麼樣的,來看一下垃圾回收吧。

正文

關於垃圾回收,.net 文件是這樣介紹的。

.NET 的垃圾回收器管理應用程式的記憶體分配和釋放。
每當有物件新建時,公共語言執行時都會從託管堆為物件分配記憶體。
只要託管堆中有地址空間,執行時就會繼續為新物件分配空間。
不過,記憶體並不是無限的。 垃圾回收器最終必須執行垃圾回收來釋放一些記憶體。
垃圾回收器的優化引擎會根據所執行的分配來確定執行回收的最佳時機。
執行回收時,垃圾回收器會在託管堆中檢查應用程式不再使用的物件,然後執行必要的操作來回收其記憶體。

那麼什麼時候垃圾回收呢?

1.系統具有低的實體記憶體。 這是通過 OS 的記憶體不足通知或主機指示的記憶體不足檢測出來。

2.由託管堆上已分配的物件使用的記憶體超出了可接受的閾值。 隨著程式的執行,此閾值會不斷地進行調整。

3.呼叫 GC.Collect 方法。 幾乎在所有情況下,你都不必呼叫此方法,因為垃圾回收器會持續執行。 此方法主要用於特殊情況和測試。

第三個.net 平臺會幫我們處理,第二個託管堆會幫我們自我調整,關鍵是第1個如何實體記憶體不足的時候就會被回收,一般時候整個作業系統記憶體佔用在15%-30%之間都是可調控的,基本不用擔心這個問題,但是為了容災性程式碼依然需要做一些判斷處理。

垃圾回收的回收機制是通過代數來實現。

為優化垃圾回收器的效能,將託管堆分為三代:第 0 代、第 1 代和第 2 代,因此它可以單獨處理長生存期和短生存期物件。

第 0 代。 這是最年輕的代,其中包含短生存期物件。 短生存期物件的一個示例是臨時變數。 垃圾回收最常發生在此代中。

第 1 代。 這一代包含短生存期物件並用作短生存期物件和長生存期物件之間的緩衝區。

第 2 代。 這一代包含長生存期物件。 長生存期物件的一個示例是伺服器應用程式中的一個包含在程式期間處於活動狀態的靜態資料的物件。

垃圾回收中未回收的物件也稱為倖存者,並會被提升到下一代:

第 0 代垃圾回收中未被回收的物件將會升級至第 1 代。
第 1 代垃圾回收中未被回收的物件將會升級至第 2 代。
第 2 代垃圾回收中未被回收的物件將仍保留在第 2 代。

因為第 0 代和第 1 代中的物件的生存期較短,因此,這些代被稱為“暫時代”。

垃圾回收的過程:

標記階段,找到並建立所有活動物件的列表。

重定位階段,用於更新對將要壓縮的物件的引用。

壓縮階段,用於回收由死物件佔用的空間,並壓縮倖存的物件。 壓縮階段將垃圾回收中倖存下來的物件移至段中時間較早的一端。

因為第 2 代回收可以佔用多個段,所以可以將已提升到第 2 代中的物件移動到時間較早的段中。 可以將第 1 代倖存者和第 2 代倖存者都移動到不同的段,因為它們已被提升到第 2 代。

然後後臺垃圾回收、大型物件堆、被動回收、延遲模式等可以作為了解。

與我們寫程式碼息息相關

垃圾回收機制與我們寫程式碼息息相關的部分是:

  1. 強引用和弱引用
  2. 針對共享 Web 承載優化
  3. 垃圾回收和效能
  4. 應用程式域資源監視

後續一節整理一下。

相關文章