簡談垃圾回收機(jī)制
什么是垃圾回收機(jī)制查库?
垃圾是堆中 unreferenced objects, 就是沒有被變量引用的變量路媚。
什么是堆?
這里就要講到程序的堆棧樊销。這個(gè)東西與數(shù)據(jù)結(jié)構(gòu)里面的堆棧有所區(qū)別整慎。
首先來講什么是棧∥唬現(xiàn)在考慮下面這種情況裤园。
void bar(int a){
printf("%d",a );
}
void foo(){
int a = 0;
bar(a);
}
int main(){
foo();
reutrn 0;
}
請問 CPU 是如何執(zhí)行這里的程序呢?由于CPU只能一條一條的執(zhí)行指令剂府,當(dāng) bar 結(jié)束時(shí)拧揽,我們?nèi)绾文軌蛑老乱粭l指令應(yīng)該執(zhí)行什么呢?其中一個(gè)想法時(shí),用 CPU 的一個(gè)寄存器保存之前 caller 的下一條命令的地址淤袜,當(dāng) bar 結(jié)束后繼續(xù)執(zhí)行就可以了痒谴。但是如果是有許多層的調(diào)用,顯然CPU的寄存器就不夠使用了铡羡。這里棧就應(yīng)運(yùn)而生了积蔚。最簡單的一個(gè)例子是,如何將遞歸調(diào)用的程序改為非遞歸的烦周。這里就是用到了 stack 這個(gè)數(shù)據(jù)結(jié)構(gòu)尽爆。在程序運(yùn)行中也一樣當(dāng)程序發(fā)生調(diào)用時(shí)。會(huì)1读慎、將 caller 的下一個(gè)指令的地址壓棧漱贱。2、將 called 函數(shù)的參數(shù)從右向左壓棧贪壳。3饱亿、將 called 函數(shù)的局部變量在棧中分配存儲(chǔ)空間蚜退。函數(shù)結(jié)束時(shí)再將這些東西出棧闰靴。
這里就有了一個(gè)問題,如果 called 程序猿手動(dòng)的分配了一個(gè)內(nèi)存空間钻注,將之中傳給 caller 函數(shù)指針可以對(duì)這些內(nèi)存空間進(jìn)行訪問蚂且。如下。那么這個(gè)分配的內(nèi)存空間是放在哪里呢幅恋?如果放在 stack 中杏死,當(dāng)函數(shù)返回時(shí),這個(gè)內(nèi)存空間就被彈出了捆交。
int * bar(int a){
int *p = malloc(a);
printf("%d",a );
return p;
}
void foo(){
int a = 1000;
int *p = bar(a);
free(p);
}
int main(){
foo();
reutrn 0;
}
為了解決這個(gè)問題淑翼,就出現(xiàn)了堆這種東西。這個(gè)與數(shù)據(jù)結(jié)構(gòu)中的堆有所區(qū)別品追。
堆區(qū)(heap) — 一般由程序員分配釋放玄括, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收肉瓦。
上例中的 malloc 出來的空間就是放在堆里面的遭京。一般來說 new 出來的對(duì)象或者內(nèi)存空間都是放在堆里面的。
更詳細(xì)的東西可以參考:C函數(shù)調(diào)用過程原理及函數(shù)棧幀分析
什么是 Garbage泞莉?
Student ali= new Student();
Student khalid= new Student();
ali=khalid;
此時(shí) ali 已經(jīng)沒有被引用了哪雕。所以你沒有辦法再訪問這個(gè)內(nèi)存空間。為了防止過多這樣無法訪問的內(nèi)存空間的出現(xiàn)鲫趁,就需要通過垃圾回收的機(jī)制來回收內(nèi)存斯嚎。在 C/C++ 中需要程序員手動(dòng)回收內(nèi)存。而在 Java 中 Java 虛擬機(jī)幫助我們完成這個(gè)事情。
什么是 GC
gc 是 Garbage Collection 的簡稱孝扛,是指找到垃圾列吼,并且回收分配給它的內(nèi)存。
什么時(shí)候會(huì)觸發(fā)GC苦始?
當(dāng)分配給程序的內(nèi)存會(huì)超過一定的門限時(shí)會(huì)觸發(fā)GC
程序會(huì)受到GC的影響么寞钥?
會(huì)的,當(dāng)GC時(shí)陌选,程序會(huì)掛起理郑。
GC的方式
1 引用計(jì)數(shù) Reference counting
這個(gè)概念和操作系統(tǒng)中的 page 從 內(nèi)存中換出,以及 hard link 刪除文件等等都是用到引用計(jì)數(shù)的概念咨油。當(dāng)被分配的內(nèi)存空間被引用時(shí)您炉,refcount++, 當(dāng)解除引用時(shí) refCount—役电。當(dāng)refCount = 0 時(shí)這個(gè)內(nèi)存空間便是垃圾赚爵。
python 就是用的 引用計(jì)數(shù)法。這種GC機(jī)制的好處就是簡單法瑟,但是會(huì)有一個(gè)問題冀膝,即循環(huán)引用的問題。
如圖所示霎挟,由于循環(huán)引用的存在窝剖,這些內(nèi)存空間在進(jìn)程結(jié)束之前是沒有辦法被回收的。并且這種方式會(huì)造成 heap 的碎片化酥夭。
2 第二種方式 mark-and-sweep 的方式
這種方式分為兩個(gè)步驟
2.1 Mark phase
GC 從 root node 遍歷引用的圖赐纱。什么是 GC roots 呢? 作為GC Roots的節(jié)點(diǎn)主要在全局性的引用與執(zhí)行上下文中熬北。從這些 roots node 開始遍歷 heap 中的對(duì)象疙描,可以訪問的標(biāo)記 1.
2.2 Sweep phase
GC 回收heap中沒有被標(biāo)記 1 的空間。
這種方式的好處是
- 避免了循環(huán)引用
- 對(duì)對(duì)象的引用關(guān)系沒有修改
壞處處是
- GC 過程中必須 掛起程序讶隐,因?yàn)楸闅v的過程如果 reference graph 改變起胰,會(huì)出現(xiàn)不一致情況。
- 造成碎片化整份。
解決碎片化的問題
3 Stop-and-Copy Garbage Collection
將內(nèi)存分為兩部分待错,在 sweep 階段將 mark 的對(duì)象copy 到另一半內(nèi)存中。
4 增量收集器
增量收集器把堆棧分為多個(gè)域烈评,每次僅從一個(gè)域收集垃圾火俄。這會(huì)造成較小的應(yīng)用程序中斷。
如果 mutator 在 collector 遍歷某對(duì)象后將其釋放(floating garbage)讲冠,那么這個(gè)對(duì)象在本次 GC 不會(huì)被回收瓜客,但在下一輪 GC 開始時(shí)會(huì)被回收。
但是增量 GC 可能會(huì)有問題。
Incremental Update
如果改變某個(gè)指針的地址谱仪,那么之前的地址會(huì)被加入一 marking stack玻熙,便于后面再次檢查,這樣就可以保證在 GC 時(shí)疯攒,所有的對(duì)象都會(huì)被遍歷到嗦随,即使指向它們的指針發(fā)生了改變。
Incremental Copying
當(dāng) mutator 訪問到 fromspace 中的對(duì)象時(shí)敬尺,立刻將之拷貝到 topspace 中枚尼。這個(gè) copy-on-demand 使用 read-barrier 來保證。什么是讀屏障呢砂吞?讀屏障就是訪問每一個(gè)對(duì)象都是通過每個(gè)對(duì)象的 redirection pointer 來進(jìn)行訪問對(duì)象署恍。這樣通過對(duì)這個(gè)指針的狀態(tài)的設(shè)置可以保證訪問的是GC copy 后面的新地址,比如當(dāng)對(duì)象正被GC移動(dòng)蜻直,指針上的顏色就會(huì)不對(duì)盯质,這個(gè)屏障就會(huì)先把指針更新為有效地址再返回
JAVA 中的GC方式
分代算法:新生代使用復(fù)制算法,老生帶使用標(biāo)記清除算法或者標(biāo)記壓縮算法概而。幾乎所有的垃圾回收期都區(qū)分新生代和老生帶呼巷。