本節內容爲類的生命週期git
對象到底是一個什麼東西?對於許多初學者而言,對象都是一個很是抽象的知識點。若是非要用一句話描述,我以爲「萬物皆對象」是對於對象最全面的概述了。本節內容中,咱們將以在富土康打工的張全蛋組裝一臺水果手機做爲例子,詳細的講解面向對象的各個方面。github
「張全蛋,你去水果公司,把他們的組裝零件需求清單帶過來~,而且還要帶上組裝的技術說明書。」車間主任吆喝着叫張全蛋辦事。張全蛋前往了水果公司,如願以償的拿到了他想要的東西,組裝零件清單上寫着:算法
技術說明上寫着:數組
限於篇幅,咱們只列舉這些,你能夠發現,咱們的組裝清單上面,事實上就是咱們手機的組成部分,須要佔用手機內部空間,而且是這個手機的重要組成參數。這就和咱們類中的屬性和字段的功能是同樣的;而技術說明,是對於這裏的具體操做,他們是一個工序,一個操做,並非一個實體,所以他們就是和咱們類中的函數是一個意思。多線程
忽然一位老員工對張全蛋說,其實啊,每一款水果手機都幾乎沒多大差異,你能夠在機器中預設好內存大小和CPU的型號,這樣你就能夠直接將模具作好了。面對這種狀況,張全蛋想出了一個絕妙的方法,那就是在構造函數中傳入參數。ide
所以咱們能夠構造出這樣一個類函數
class FruitPhone { public FruitPhone(int msize,string cpuType) { CpuType = cpuType; MemSize = msize; } public string ScreenType{get;set} public string CpuType{get;set;} public int MemSize{get;set;} public int Battery{get;set;} void Make() { //todo } void Open() { //todo } }
對象就像個體的人,生而入世,死而離世。咱們的故事就從對象之生開始吧。首先,看看在上面的例子中,一個對象是如何出生的。ui
FruitPhone p = new FruitPhone(2,"A12");
咱們經過調用構造函數,成功的創造了一個手機對象,在手機被建立的同時,雖然咱們尚未組裝好屏幕一類的,可是咱們在手機模具中也須要預留他們的空間,所以在對象實例化的時候,其內部的每一個字段都會被初始化。spa
對於屏幕和電池一類的,咱們後續可能會根據成本等等進行調整,對
象的出生也只是完成了對必要字段的初始化操做,其餘數據要經過後面的操做來完成。例如對屬性賦值,經過方法獲取必要的信息等。操作系統
關於內存的分配,首先應該瞭解分配在哪裏的問題。CLR 管理內存的區域,主要有三塊,分別爲:
對於分配在堆棧上的局部變量來講,操做系統維護着一個堆棧指針來指向下一個自由空間的地址,而且堆棧的內存地址是由高位到低位向下填充。
而對於引用類型的實例分配於託管堆上,而線程棧倒是對象生命週期開始的地方。對 32 位處理器來講,應用程序完成進程初始化後,CLR 將在進程的可用地址空間上分配一塊保留的地址空間,它是進程(每一個進程可以使用 4GB)中可用地址空間上的一塊內存區域,但並不對應於任何物理內存,這塊地址空間便是託管堆。託管堆又根據存儲信息的不一樣劃分爲多個區域,其中最重要的是垃圾回收堆(GC Heap)和加載堆(Loader Heap),GC Heap 用於存儲對象實例,受 GC 管理;Loader Heap 又分爲 High-Frequency Heap、Low-Frequency Heap 和 Stub Heap,不一樣的堆上又存儲不一樣的
信息。Loader Heap 最重要的信息就是元數據相關的信息,也就是 Type 對象,每一個 Type 在 Loader Heap 上體現爲一個 Method Table(方法表),而 Method Table 中則記錄了存儲的元數據信息,例如基類型、靜態字段、實現的接口、全部的方法等等。Loader Heap 不受 GC 控制,其生命週期爲從建立到 AppDomain 卸載。
對於本例中的對象建立,首先會在棧中聲明一個指向堆中數據的指針(引用),它佔用4個字節,而後調用newobj指令,搜索該類是否含有父類,若是有,則從父類開始分配內存,對於本例中,FruitPhone對象所須要的內存爲4字節的string引用兩個,4字節的int*2。實例對象所佔的字節總數還要加上對象附加成員所需的字節總數,其中附加成員包括 TypeHandle 和 SyncBlockIndex,共計 8 字節(在 32 位 CPU 平臺下),共計24字節。
CLR 在當前 AppDomain 對應的託管堆上搜索,找到一個未使用的 20 字節的連續空間,併爲其分配該內存地址。事實上,GC 使用了很是高效的算法來知足該請求,NextObjPtr 指針只須要向前推動 20 個字節,並清零原 NextObjPtr 指針和當前 NextObjPtr 指針之間的字節,
而後返回原 NextObjPtr 指針地址便可,該地址正是新建立對象的託管堆地址,也就是p引用指向的實例地址。而此時的 NextObjPtr 仍指向下一個新建對象的位置。注意,棧的分配是向
低地址擴展,而堆的分配是向高地址擴展。
最後,調用對象構造器,進行對象初始化操做,完成建立過程。該構造過程,又可細分爲
如下幾個環節:
上述過程,基本完成了一個引用類型建立、內存分配和初始化的整個流程,然而該過程只能看做是一個簡化的描述,實際的執行過程更加複雜,涉及到一系列細化的過程和操做。
(插入內存圖像)
靜態字段的內存分配和釋放,又有何不一樣?
靜態字段也保存在方法表中,位於方法表的槽數組後,其生命週期爲從建立到 AppDomain
卸載。所以一個類型不管建立多少個對象,其靜態字段在內存中也只有一份。靜態字段只能由靜
態構造函數進行初始化,靜態構造函數確保在類型任何對象建立前,或者在任何靜態字段或方法
被引用前執行,其詳細的執行順序請參考相關討論。
在這一部分,咱們首先觀察對象之死,以此反思和體味人類入世的哲學,二者相比較,也會給咱們更多關於本身的啓示。對象的生命週期由 GC 控制,其規則大概是這樣:GC 管理全部的託管堆對象,當內存回收執行時,GC 檢查託管堆中再也不被使用的對象,並執行內存回收操做。不被應用程序使用的對象,指的是對象沒有任何引用。關於如何回收、回收的時刻,以及遍歷可回收對象的算法,是較爲複雜的問題,咱們將在 後續進行深度探討。不過,這個回收的過程,一樣使咱們感慨。大天然就是那個看不見的 GC,造物而又終將萬物回收,沒法改變。咱們所能作到的是,將生命的週期拓寬、延長、書寫得更加精彩
若是個人文章幫到了你,請在博客園下面點一個推薦,在github項目頁面點一顆星,謝謝
你必須知道的.NET