ASP.Net?Core中的內(nèi)存和GC機(jī)制
托管代碼
在 .NET 中, CLR(Common Language Runtime) 負(fù)責(zé)提取托管代碼并編譯成機(jī)器語言,然后執(zhí)行它。在此過程中,CLR 提供自動(dòng)內(nèi)存管理、安全邊界、類型安全等服務(wù),保證了代碼安全。
托管代碼指在其執(zhí)行過程中由 CLR(Common Language Runtime) 管理的代碼,托管代碼是可在 .NET 上運(yùn)行得一種高級(jí)語言(C#、F#等),編寫的托管代碼被編譯后會(huì)被生成 中間語言(IL)。
CLR 有 .NET Core/.NET5+、Mono、.NET Framework 等實(shí)現(xiàn),托管代碼生成的文件(IL代碼)不能被操作系統(tǒng)直接運(yùn)行,需要 CLR 的實(shí)現(xiàn)(如 .NET5) 托管運(yùn)行,托管過程中對(duì)其再次編譯生成二進(jìn)制代碼(JIT編譯)。
中間語言(IL)有時(shí)也稱為公共中間語言 (CIL) 或 Microsoft 中間語言 (MSIL)。
自動(dòng)內(nèi)存管理
自動(dòng)內(nèi)存管理是 CLR 的功能之一,它可以為應(yīng)用程序管理內(nèi)存的分配和釋放,托管代碼被執(zhí)行時(shí),由 CLR 進(jìn)行內(nèi)存管理,保證了內(nèi)存安全。
垃圾回收
GC
GC(garbage collector)中文譯為垃圾回收器,.NET 中的 GC 指的是 CLR 中的自動(dòng)內(nèi)存管理器,GC 負(fù)責(zé)管理 .NET 程序的內(nèi)存分配和釋放。
GC 的優(yōu)點(diǎn)如下:
自動(dòng)管理內(nèi)存,不必手動(dòng)分配和釋放;
高效管理托管堆上的對(duì)象;
智能回收對(duì)象,清除內(nèi)存;
內(nèi)存安全:避免野指針、懸空指針等情況造成嚴(yán)重錯(cuò)誤;
內(nèi)存
物理內(nèi)存
物理內(nèi)存是物理內(nèi)存條上的內(nèi)存空間,是物理機(jī)器真實(shí)的容量大小。
虛擬內(nèi)存
虛擬內(nèi)存(Virtual Memory)是計(jì)算機(jī)操作系統(tǒng)進(jìn)行內(nèi)存管理的一種技術(shù),它可以將多個(gè)硬件、非連續(xù)地址的碎片空間組合起來,形成進(jìn)程上可識(shí)別的連續(xù)內(nèi)存空間。
虛擬內(nèi)存由操作系統(tǒng)進(jìn)行支持,如 Windows 上的虛擬內(nèi)存,Linux 上的交互空間,虛擬內(nèi)存需要操作系統(tǒng)映射到真實(shí)的內(nèi)存地址空間才能使用。虛擬內(nèi)存調(diào)度方式有分頁式、段式、段頁式3種,讀者感興趣可自行查閱資料。
現(xiàn)代操作系統(tǒng)都采用了虛擬內(nèi)存管理技術(shù),通過對(duì)物理存儲(chǔ)設(shè)備的抽象,操作系統(tǒng)調(diào)度外存當(dāng)作內(nèi)存使用,提供了比物理內(nèi)存更大的內(nèi)存范圍。
這些存儲(chǔ)設(shè)備組成的內(nèi)存稱為虛擬地址空間,而用戶(開發(fā)者)接觸到的地址是虛地址,并不是真實(shí)的物理地址。虛擬空間大大拓展了內(nèi)存,使得系統(tǒng)可以同時(shí)運(yùn)行多道程序而不“吃力”。
虛擬地址空間分為兩部分:用戶空間、內(nèi)核空間,每個(gè)程序運(yùn)行時(shí)的會(huì)消耗兩種空間。在 Linux 中比例是 3:1,在 Windows 中是 2:2。
.NET 內(nèi)存組成
.NET 中,內(nèi)存分為非托管內(nèi)存、托管內(nèi)存。
.NET Core/.NET5+ 有一個(gè)稱為 dotnet 的驅(qū)動(dòng)程序,此驅(qū)動(dòng)程序用于執(zhí)行命令或運(yùn)行 .NET 程序。當(dāng)我們使用 dotnet 命令運(yùn)行一個(gè) .dll 文件時(shí),操作系統(tǒng)會(huì)啟動(dòng) dotnet 驅(qū)動(dòng)程序,此時(shí)會(huì)分配操作系統(tǒng)內(nèi)存資源、dotnet 驅(qū)動(dòng)程序內(nèi)存資源,這一部分即非托管資源,其中 dotnet 部分的內(nèi)存包含了 CLR 等部件的內(nèi)存。即使你并沒有使用到 C/C++ 等非托管代碼或者使用非托管資源,也會(huì)使用到非托管內(nèi)存。
接下來 CLR 將初始化新進(jìn)程,CLR 將為其分配托管內(nèi)存(托管堆),這段托管內(nèi)存是一個(gè)連續(xù)的地址空間區(qū)域。.NET 安全代碼只能使用托管內(nèi)存,不能直接使用物理內(nèi)存,垃圾收集器會(huì)為安全代碼在托管堆上分配和釋放虛擬內(nèi)存。
顯然, dotnet 的工作原理十分復(fù)雜,筆者沒有能力講清楚,感興趣的讀者可以自行查閱資料。
CLR 中的內(nèi)存
微軟 .NET CLR 文檔中寫道:By default, on 32-bit computers, each process has a 2-GB user-mode virtual address space.
即在 32 位系統(tǒng)中,.NET 進(jìn)程會(huì)使用 2GB 的用戶模式虛擬內(nèi)存,其虛擬地址空間的表示范圍是 0x00000000 到 0x7fff;而 64 位系統(tǒng)中,地址范圍是 0x000'00000000 到0x7FFF'FFFFFFFF,約等于 16TB。
從以上信息,我們知道 .NET 程序會(huì)消耗比較多的虛擬內(nèi)存,如果在 64 位操作系統(tǒng)上運(yùn)行 .NET 程序,其用戶模式虛擬地址空間可能遠(yuǎn)遠(yuǎn)大于 2GB。
編寫一個(gè) "c1" 程序,其代碼如下:
static void Main(string[] args) { Console.WriteLine("Hello World!"); Console.Read(); }
在 Linux 中使用 dotnet xx.dll 命令運(yùn)行程序,然后查看其占用的資源:
VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3.1g 0.0g 0.0g S 0.3 0.3 0:00.83 dotnet
使用 dotnet-counters 查看 dotnet 進(jìn)程:
GC Heap Size (MB) 0 Gen 0 GC Count (Count / 1 sec) 0 Gen 0 Size (B) 0 Gen 1 GC Count (Count / 1 sec) 0 Gen 1 Size (B) 0 Gen 2 GC Count (Count / 1 sec) 0 Gen 2 Size (B) 0 LOH Size (B) 0
注:使用 dotnet run 運(yùn)行 .NET 項(xiàng)目,會(huì)出現(xiàn) dotnet、c1 兩個(gè)進(jìn)程,可以看到會(huì)產(chǎn)生 dotnet 和 c1 兩個(gè)進(jìn)程,dotnet 是驅(qū)動(dòng)程序,dotnet 啟動(dòng)后,CLR 會(huì)將. dll 程序集編譯,并初始化啟動(dòng)一個(gè)進(jìn)程。
CLR 中的虛擬地址空間需要位于一個(gè)地址塊中,因?yàn)樵谡?qǐng)求虛擬內(nèi)存分配時(shí),虛擬內(nèi)存管理器必須找到滿足需求的單個(gè)可用塊,例如就算存在大于 2GB 的虛擬地址空間,但如果不是連續(xù)的,則會(huì)分配失敗。如果沒有足夠的可供保留的虛擬地址空間或可供提交的物理空間,則可能會(huì)用盡內(nèi)存。
CLR 虛擬內(nèi)存狀態(tài)
CLR 中的虛擬內(nèi)存可以有三種狀態(tài):
State | Description |
---|---|
Free 可用 | The block of memory has no references to it and is available for allocation. 內(nèi)存塊沒有對(duì)它的引用,可以進(jìn)行分配 |
Reserved保留 | The block of memory is available for your use and cannot be used for any other allocation request. 該內(nèi)存塊可供您使用,不能用于任何其他分配請(qǐng)求 However, you cannot store data to this memory block until it is committed. 但是,在提交數(shù)據(jù)之前,不能將數(shù)據(jù)存儲(chǔ)到此內(nèi)存塊中 |
Committed已提交 | The block of memory is assigned to physical storage. 內(nèi)存塊已指派給物理存儲(chǔ) |
內(nèi)存分配
CLR 在初始化新進(jìn)程時(shí),會(huì)為進(jìn)程保留一個(gè)連續(xù)的地址空間區(qū)域,這個(gè)地址空間被稱為托管堆。托管堆中維護(hù)著一個(gè)指針,最初此指針指向托管堆的基址,這個(gè)指針是向后移動(dòng)的。當(dāng)需要分配內(nèi)存時(shí),CLR 便會(huì)分配位于此指針后的內(nèi)存區(qū)域,同時(shí)指針指向此對(duì)象地址空間之后的位置。
由于 CLR 通過向指針添加值來為對(duì)象分配內(nèi)存,所以它的分配速度幾乎跟從堆棧中分配內(nèi)存速度一樣快;而且連續(xù)分配的新對(duì)象連續(xù)存儲(chǔ)在托管堆中,程序可以快速地訪問這些對(duì)象。
當(dāng) GC 回收內(nèi)存時(shí),一些對(duì)象釋放后內(nèi)存會(huì)被回收,這樣托管堆地內(nèi)存處于碎片化,之后整個(gè)內(nèi)存段會(huì)被壓縮,重新組成連連續(xù)的內(nèi)存段,指針會(huì)被重置到對(duì)象的末尾。
當(dāng)然,大對(duì)象堆(LOH)回收并不會(huì)壓縮內(nèi)存段,這一點(diǎn)我們后面再討論。
內(nèi)存釋放
垃圾回收的條件
根據(jù)微軟官方文檔,整理的垃圾回收條件如下:
- 系統(tǒng)物理內(nèi)存不足;
- 托管堆分配的內(nèi)存已超出可接受閾值;(當(dāng)然,這個(gè)閾值會(huì)被動(dòng)態(tài)調(diào)整)
- 手動(dòng)調(diào)用 GC 類的 API(例如 GC.Collect);
托管堆
本機(jī)堆(Native Heap)
前面提到過,.NET 的內(nèi)存有非托管內(nèi)存和托管內(nèi)存。CLR 運(yùn)行的進(jìn)程,存在本機(jī)堆和托管堆兩種內(nèi)存堆,本機(jī)內(nèi)存堆通過 Windows API 的 VirtualAlloc 函數(shù)分配,提供給 操作系統(tǒng)和 CLR 使用,用于非托管代碼所需的內(nèi)存。
托管堆(Managed Heap)
關(guān)于托管堆,前面已經(jīng)寫了,這里不再贅述。
托管堆代數(shù)
托管堆中的內(nèi)存被分為三代,分別使用0、1、2 標(biāo)識(shí),GC 分配的內(nèi)存首先在 0 代托管堆中,當(dāng)進(jìn)行垃圾回收時(shí),如果對(duì)象沒有被釋放,則將其升級(jí)并存儲(chǔ)到 1 代托管堆中。1 代托管堆進(jìn)行內(nèi)存回收時(shí),不被釋放的對(duì)象也會(huì)被升級(jí)到 2 代內(nèi)存中,然后 1 代內(nèi)存堆進(jìn)行空間壓縮。
托管堆的管理是 GC 負(fù)責(zé)的,而 GC 進(jìn)行內(nèi)存分配和釋放,使用了 GC 算法。
GC 算法基于以下理論:
- ① 壓縮托管堆的一部分內(nèi)存要比壓縮整個(gè)托管堆速度快;
- ② 較新的對(duì)象生命周期較短,較舊的對(duì)象生命周期較長(zhǎng);
- ③ 較新的對(duì)象趨向于相互關(guān)聯(lián),并且大約在同一時(shí)間被應(yīng)用程序訪問;
我們必須深刻理解這些理論,才能深入理解托管堆的設(shè)計(jì)。
關(guān)于 0 到 2 代堆,其基本說明如下:
- 0 代:0 代中的對(duì)象擁有短暫的生命周期,垃圾回收最常發(fā)生在此代中;
- 1 代:作為生命周期較短和生命周期較長(zhǎng)對(duì)象的緩沖區(qū)。
- 2 代:存儲(chǔ)生命周期長(zhǎng)的對(duì)象;0、1 代沒被回收而升級(jí)的對(duì)象會(huì)升級(jí)到 2 代中,靜態(tài)數(shù)據(jù)等則會(huì)一開始就分配到 2代。
在 .NET 5 之前,.NET 有 SOH(小對(duì)象堆)、LOH(大對(duì)象堆);在 .NET 5 中,出現(xiàn)了 POH ;
小對(duì)象堆的內(nèi)存段有 0、1、2 代堆;
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解.NET Core 使用HttpClient SSL請(qǐng)求出錯(cuò)的解決辦法
這篇文章主要介紹了.NET Core 使用HttpClient SSL請(qǐng)求出錯(cuò)的解決辦法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-03-03ASP.NET下使用WScript.Shell執(zhí)行命令
ASP.NET下有自己的執(zhí)行CMD命令的方式,這里用WScript.Shell似有畫蛇添足之嫌,但是我們也不能排除真的有機(jī)器禁用了.NET的相關(guān)類,未雨綢繆嘛。當(dāng)然也不僅僅局限于WScript.Shell,只要是ASP中能用的組件,統(tǒng)統(tǒng)都可以用于ASP.NET中,而且還更方便!2008-05-05asp.net連接數(shù)據(jù)庫讀取數(shù)據(jù)示例分享
這篇文章主要介紹了asp.net連接數(shù)據(jù)庫讀取數(shù)據(jù)示例,大家參考使用吧2014-01-01asp.net中SqlCacheDependency緩存技術(shù)概述
這篇文章主要介紹了asp.net中SqlCacheDependency緩存技術(shù)概述,是大型web程序設(shè)計(jì)中常用的技術(shù),本文對(duì)此進(jìn)行了較為詳細(xì)的描述,需要的朋友可以參考下2014-08-08.NET?實(shí)現(xiàn)啟動(dòng)時(shí)重定向程序運(yùn)行路徑及?Windows?服務(wù)運(yùn)行模式部署的方法
這篇文章主要介紹了.NET?實(shí)現(xiàn)啟動(dòng)時(shí)重定向程序運(yùn)行路徑及?Windows?服務(wù)運(yùn)行模式部署,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09