一.概念
堆疊是什麼?
在說堆疊之前,先說說記憶體是神馬?
記憶體:程式在執行的過程,電腦需要不斷通過CPU進行計算,這個計算的過程會讀取併產生運算的資料,這些資料需要一個儲存容器存放。這個容器,這就是記憶體了。
我們知道C#是強型別語言,每個變數和常量都有一個型別,即所有的資料都會有一個型別。在.Net中,所有的型別又分為值型別和引用型別。簡單介紹一下。
值型別:使用int,float,struct,enum關鍵字直接繼承自System.ValueType定義的型別。
引用型別:Class,interface,string,delegate繼承自System.Object。
值型別和引用型別有什麼區別吶?
主要區別在於值型別物件固定大小,引用型別物件可以指向任何型別,無法確定其大小。因此記憶體區域分為棧和堆。值型別儲存在棧上,引用型別儲存在堆上。
二.棧和堆
棧:作業系統會為每條執行緒分配一定的空間,Windows為1M。在棧上的成員不受GC管理器的控制,直接由作業系統分配。或者理解為儲存短期較小資料塊,超出作用域,自動釋放。
堆:主要用來存放引用型別物件,不需要我們人工去分配和釋放,由GC管理器託管。或者理解為儲存長期較大資料塊,超出作用域並不會被釋放,保持被分配的狀態。GC會處理未引用的堆記憶體。
不同於值型別直接在棧中存放值,引用型別還需要在棧中存放一個指向堆中物件的值的地址。
值型別,引用型別區別詳見:https://www.cnblogs.com/u3ddjw/p/6756536.html
值型別和引用型別之間可以互轉嗎?
這個需求,是很常見,所以要說一下。答案是肯定的,當然可以,這裡就需要提到裝箱(值型別===>引用型別),拆箱(引用型別===>值型別)。
裝箱:
I.分配堆記憶體
II.將值型別的例項欄位拷貝到新分配的堆記憶體中
III.返回託管堆中新分配的物件的地址在棧中。這個地址就是一個指向物件的引用。
拆箱:
I.檢查物件例項,確保它是給定值型別的一個裝箱值。
II.將該值例項複製到值型別變數中。
三.GC垃圾管理器
①GC和堆記憶體聯絡
上述說到棧是作業系統實時自動分配釋放,不需要我們去管理。堆記憶體也是由GC控制管理。但是GC並不是實時管理的,是需要通過程式設計師手動或系統定時觸發的。因為GC是一個耗時的操作,可能在有些系統中觸發的不合時宜(明顯示卡頓)。所以,GC也需要優化,需要控制在合事宜的情況觸發。比如遊戲中我們需要在切換Loading時觸發GC,而在遊戲戰鬥中控制不能被觸發。
因此優化GC,就是優化堆記憶體,就是儘量減少堆記憶體,及時回收堆記憶體。
②GC是什麼?
GC即(Gabarage Collector,垃圾回收器),歸屬於CLR(公共語言執行時,可以理解為.Net虛擬機器),專門用於回收託管堆記憶體的
③GC如何釋放堆記憶體的?
GC清理堆時,GC收集器會通過一定的演算法清理堆中的物件,並且版本不同演算法也不同。標記-壓縮演算法:通過一個圖的資料結構來收集物件的根,這個根就是引用地址。可以理解為指向託管堆的關係線。當觸發這個演算法時,會檢查圖中的每個根是否可達,如果可達,則對其標記,然後在堆上找到剩餘沒有標記的物件進行刪除,這樣,
那些不再使用的堆中物件就刪除了。
為了優化記憶體結構,減少在圖中搜尋的成本,GC機制又為每個託管堆物件定義了一個屬性,將每個物件分為三個等級,0代,1代,2代。
每當new一個物件的時候,該物件會被定義為第0代,當GC開始回收的時候,先從第0代開始,在這樣一次回收動作之後,0代沒有被回收的物件則被定義為第1代,當回收第1代的時候,第1代中沒有被清理的物件會被定義為第2代。
CLR會為0/1/2代選擇一個預算的容量,0代通常為256k-4mb預算,1代為512-4m,2代不受限制,最大可擴充至作業系統的整個記憶體空間。代數越長說明這個物件經歷了回收的次數越多,那就意味著該物件是最不容易被清除的。這種分代的思想將物件分割成新老物件,進而配對不同的清除條件,這種巧妙的思想避免了直接清理整個堆(卡頓後果)。
擴充套件說明:
比如Unity採用貝姆垃圾回收機制與.Net垃圾回收器相比一直有很大的限制。
I.貝姆垃圾回收:無分代\並行,執行時所有執行緒阻塞;每次標記都會訪問所有可達的物件(窮舉搜尋垃圾)。這種方式極有可能在短時間造成幀率下降,影響玩家體驗。
II.分代回收:效率高很多。
參考:https://1996v.cnblogs.com/p/9037603.html?from=timeline&isappinstalled=0