.NET Core CSharp初級篇1-7 類的生命歷程


.NET Core CSharp初級篇 1-7

本節內容為類的生命周期

引言

對象究竟是一個什么東西?對於許多初學者而言,對象都是一個非常抽象的知識點。如果非要用一句話描述,我覺得“萬物皆對象”是對於對象最全面的概述了。本節內容中,我們將以在富土康打工的張全蛋組裝一台水果手機作為例子,詳細的講解面向對象的各個方面。

對象類的構造

“張全蛋,你去水果公司,把他們的組裝零件需求清單帶過來~,並且還要帶上組裝的技術說明書。”車間主任吆喝着叫張全蛋辦事。張全蛋前往了水果公司,如願以償的拿到了他想要的東西,組裝零件清單上寫着:

  • amoled屏幕*1
  • 電池3000MA *1
  • CPU*1
  • 內存*1

技術說明上寫着:

  • 組裝零件:屏幕放置在頂部,電池在底部,中間夾着PCB板,PCB上面封住CPU和內存
  • 開機方法:長按開機鍵三秒

限於篇幅,我們只列舉這些,你可以發現,我們的組裝清單上面,事實上就是我們手機的組成部分,需要占用手機內部空間,並且是這個手機的重要組成參數。這就和我們類中的屬性和字段的功能是一樣的;而技術說明,是對於這里的具體操作,他們是一個工序,一個操作,並不是一個實體,因此他們就是和我們類中的函數是一個意思。

突然一位老員工對張全蛋說,其實啊,每一款水果手機都幾乎沒多大差別,你可以在機器中預設好內存大小和CPU的型號,這樣你就可以直接將模具做好了。面對這種情況,張全蛋想出了一個絕妙的方法,那就是在構造函數中傳入參數。

因此我們可以構造出這樣一個類

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
    }
}

對象的出生

對象就像個體的人,生而入世,死而離世。我們的故事就從對象之生開始吧。首先,看看在上面的例子中,一個對象是如何出生的。

FruitPhone p = new FruitPhone(2,"A12");

我們通過調用構造函數,成功的創造了一個手機對象,在手機被創建的同時,雖然我們還沒有組裝好屏幕一類的,但是我們在手機模具中也需要預留他們的空間,因此在對象實例化的時候,其內部的每個字段都會被初始化。

對於屏幕和電池一類的,我們后續可能會根據成本等等進行調整,對
象的出生也只是完成了對必要字段的初始化操作,其他數據要通過后面的操作來完成。例如對屬性賦值,通過方法獲取必要的信息等。

對象在內存中的創建過程

關於內存的分配,首先應該了解分配在哪里的問題。CLR 管理內存的區域,主要有三塊,分別為:

  • 線程的堆棧,用於分配值類型實例。堆棧主要由操作系統管理,而不受垃圾收集器的控制,當值類型實例所在方法結束時,其存儲單位自動釋放。棧的執行效率高,但存儲容量有限。
  • GC 堆,用於分配小對象實例。如果引用類型對象的實例大小小於 85000 字節,實例將被分配在 GC 堆上,當有內存分配或者回收時,垃圾收集器可能會對 GC 堆進行壓縮,詳情見后文講述。
  • LOH(Large Object Heap)堆,用於分配大對象實例。如果引用類型對象的實例大小不小於 85000 字節時,該實例將被分配到 LOH 堆上,而 LOH 堆不會被壓縮,而且只在完全 GC 回收時被回收。

對於分配在堆棧上的局部變量來說,操作系統維護着一個堆棧指針來指向下一個自由空間的地址,並且堆棧的內存地址是由高位到低位向下填充。

而對於引用類型的實例分配於托管堆上,而線程棧卻是對象生命周期開始的地方。對 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 仍指向下一個新建對象的位置。注意,棧的分配是向
低地址擴展,而堆的分配是向高地址擴展。

最后,調用對象構造器,進行對象初始化操作,完成創建過程。該構造過程,又可細分為
以下幾個環節:

  • 構造 FruitPhone 類型的 Type 對象,主要包括靜態字段、方法表、實現的接口等,並將其
    分配在上文提到托管堆的 Loader Heap 上。
  • 初始化 p 的兩個附加成員:TypeHandle 和 SyncBlockIndex。將 TypeHandl
    e 指針指向 Loader Heap 上的 MethodTable,CLR 將根據 TypeHandle 來定位具體的 Type;
    將 SyncBlockIndex 指針指向 Synchronization Block 的內存塊,用於在多線程環境下對實例
    對象的同步操作。
  • 調用 FruitPhone 的構造器,進行實例字段的初始化。實例初始化時,會首先向上遞歸執
    行父類初始化,直到完成 System.Object 類型的初始化,然后再返回執行子類的初始化,直到
    執行 FruitPhone 類為止。以本例而言,初始化過程為首先執行 System.Object 類,直接執行 FruitPhone。最終,newobj 分配的托管堆的內存地址,被傳遞給 FruitPhone 的 thi
    s 參數,並將其引用傳給棧上聲明的 p。

上述過程,基本完成了一個引用類型創建、內存分配和初始化的整個流程,然而該過程只能看作是一個簡化的描述,實際的執行過程更加復雜,涉及到一系列細化的過程和操作。

(插入內存圖像)

補充

靜態字段的內存分配和釋放,又有何不同?

靜態字段也保存在方法表中,位於方法表的槽數組后,其生命周期為從創建到 AppDomain
卸載。因此一個類型無論創建多少個對象,其靜態字段在內存中也只有一份。靜態字段只能由靜
態構造函數進行初始化,靜態構造函數確保在類型任何對象創建前,或者在任何靜態字段或方法
被引用前執行,其詳細的執行順序請參考相關討論。

對象的消亡

在這一部分,我們首先觀察對象之死,以此反思和體味人類入世的哲學,兩者相比較,也會給我們更多關於自己的啟示。對象的生命周期由 GC 控制,其規則大概是這樣:GC 管理所有的托管堆對象,當內存回收執行時,GC 檢查托管堆中不再被使用的對象,並執行內存回收操作。不被應用程序使用的對象,指的是對象沒有任何引用。關於如何回收、回收的時刻,以及遍歷可回收對象的算法,是較為復雜的問題,我們將在 后續進行深度探討。不過,這個回收的過程,同樣使我們感慨。大自然就是那個看不見的 GC,造物而又終將萬物回收,無法改變。我們所能做到的是,將生命的周期拓寬、延長、書寫得更加精彩

如果我的文章幫到了你,請在博客園下面點一個推薦,在github項目頁面點一顆星,謝謝

Reference

你必須知道的.NET

Github

BiliBili主頁

WarrenRyan's Blog

博客園


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM