C#垃圾回收和托管堆
每次使用一種資源,都必須要為這種資源類型分配內(nèi)存
資源的生命周期
調(diào)用IL指令newobj,為生成的類型分配內(nèi)存(一般使用c#new 操作符完成)
初始化內(nèi)存,設(shè)置內(nèi)存狀態(tài)并使資源可用,類型的構(gòu)造器負(fù)責(zé)設(shè)置初始狀態(tài)
訪問類型的成員來使用資源
摧毀資源狀態(tài)以進行清理
釋放內(nèi)存。垃圾回收獨自負(fù)責(zé)這一步
C#的new操作符會導(dǎo)致CLR執(zhí)行以下步驟
計算類型字段所需的字節(jié)數(shù),加上對象開銷所需的字節(jié)數(shù)
檢查區(qū)域中是否有可以分配這個對象所需的字節(jié)數(shù),如果空間足夠,就放入對象,當(dāng)空間不足時,CLR就會執(zhí)行垃圾回收
垃圾回收算法(CLR使用的是引用跟蹤算法)
引用計數(shù)
當(dāng)一對象被另一個對象引用時或不再被引用時,引用計數(shù)就會相應(yīng)的增加或減少
使用引用計數(shù)算法只需要遍歷所有引用類型變量,當(dāng)引用計數(shù)為0時就表示沒有任何對象引用該對象,可以進行回收
引用計數(shù)最大的問題就是無法處理循環(huán)引用,比如2個或多個對象互相引用,但這些對象都不再被外部使用,這時這2個對象的引用計數(shù)都不會變?yōu)?,因此也無法被回收
引用跟蹤
引用跟蹤算法只關(guān)心引用類型的變量,所有引用類型的變量都被稱為根
當(dāng)進行垃圾回收時,CLR會檢查所有根,如果一個根包含null則忽略,并檢查下個根
如果不為null,則這個對象就會被標(biāo)記,并且遞歸檢查這個對象的所有引用,如果這些引用不為空則也會被標(biāo)記
在標(biāo)記過程中如果在標(biāo)記某個對象時發(fā)現(xiàn)它已經(jīng)被標(biāo)記,那么會使用循環(huán)引用檢測算法檢測這個對象是不是循環(huán)引用
檢查一遍后,被標(biāo)記的對象就是可達對象,沒有被標(biāo)記的對象和被定義為循環(huán)引用的對象就是不可達對象,不可達對象就可以進行回收
代
托管堆會被分成3個部分,分別稱為0代,1代,2代
當(dāng)新對象生成時,對象會被分配到0代內(nèi)存中
如果一個新的對象分配后,造成0代超預(yù)算,那么就會觸發(fā)垃圾回收
在垃圾回收后,0代內(nèi)存中存活的對象會被轉(zhuǎn)移到1代內(nèi)存中
在不斷的內(nèi)存遷移中,1代內(nèi)存也會慢慢擴大,所以當(dāng)1代內(nèi)存容量達到預(yù)算時,垃圾收集器就會回收0代和1代中的對象
在垃圾回收后,1代內(nèi)存中存活的對象會被轉(zhuǎn)移到2代內(nèi)存中
當(dāng)2代內(nèi)存也達到預(yù)算時,CLR就會觸發(fā)所有區(qū)域的垃圾回收
當(dāng)3個區(qū)域都被占滿時,應(yīng)用就停止響應(yīng)了
將托管堆分區(qū)的好處
垃圾回收時,回收一部分,速度快于回收整個堆
存活時間長的對象大概率是不需要回收的,在每次回收時將幸存的對象向后遷移到后面的內(nèi)存區(qū)域,這樣就可以保證,前幾個內(nèi)存區(qū)域儲存的對象都是新對象,進行垃圾回收時找到垃圾的幾率也更高
3個內(nèi)存區(qū)域并不是平均分配的,而是動態(tài)調(diào)節(jié)的
如果在回收0代內(nèi)存后存活下來的對象很少,這時就會減小0代內(nèi)存的預(yù)算,空間小意味著垃圾回收會更頻繁,但每次回收做的事也少了
如果回收0代內(nèi)存后發(fā)現(xiàn)還有很多對象存活,這時就會擴大0帶內(nèi)存的預(yù)算,這樣垃圾回收的次數(shù)將減少,但每次回收時,回收的內(nèi)存也越多
在其它2個內(nèi)存區(qū)域也會使用類似的算法控制預(yù)算
垃圾回收觸發(fā)條件
創(chuàng)建對象時,發(fā)現(xiàn)空間不夠
當(dāng)任何一個內(nèi)存域的內(nèi)存超出預(yù)算時
代碼顯式調(diào)用GC.Collect方法,這樣會觸發(fā)所有托管堆的垃圾回收,,并且垃圾回收是自動觸發(fā)的,一般不需要顯示調(diào)用
系統(tǒng)報告可用內(nèi)存低時