GC如其名,就是垃圾收集,當然這里僅就內(nèi)存而言底洗。Garbage Collector(垃圾收集器)以應用程序的root為基礎(chǔ)款筑,遍歷應用程序在Heap上動態(tài)分配的所有對象智蝠,通過識別它們是否被引用來確定哪些對象是已經(jīng)死亡的腾么、哪些仍需要被使用。已經(jīng)不再被應用程序的root或者別的對象所引用的對象就是已經(jīng)死亡的對象杈湾,即所謂的垃圾解虱,需要被回收(回收的是該對象占用的內(nèi)存空間)。這就是GC工作的原理漆撞。為了實現(xiàn)這個原理殴泰,GC有多種算法。比較常見的算法有Reference Counting浮驳,Mark Sweep悍汛,Copy Collection等等。目前主流的虛擬系統(tǒng).NET CLR至会,Java VM和Rotor都是采用的Mark Sweep算法离咐。
JVM堆相關(guān)知識??? 為什么先說JVM堆???
??JVM的堆是Java對象的活動空間,程序中的類的對象從中分配空間奋献,其存儲著正在運行著的應用程序用到的所有對象健霹。這些對象的建立方式就是那些new一類的操作,當對象無用后瓶蚂,是GC來負責這個無用的對象糖埋。
JVM堆???
?(1) 新域:存儲所有新成生的對象????新域會被分為3個部分:1.第一個部分叫Eden。2.另兩個部分稱為輔助生存空間(幼兒園)窃这,我這里一個稱為A空間(From sqace)瞳别,一個稱為B空間(To Space)。
(2) 舊域:新域中的對象杭攻,經(jīng)過了一定次數(shù)的GC循環(huán)后祟敛,被移入舊域???
?(3)永久域:存儲類和方法對象,從配置的角度看兆解,這個域是獨立的馆铁,不包括在JVM堆內(nèi)。默認為4M锅睛。?
垃圾回收的原因
從計算機組成的角度來講埠巨,所有的程序都是要駐留在內(nèi)存中運行的。而內(nèi)存是一個限制因素(大小)现拒。除此之外辣垒,托管堆也有大小限制。因為地址空間和存儲的限制因素印蔬,托管堆要通過垃圾回收機制勋桶,來維持它的正常運作,保證對象的分配,盡可能不造成“內(nèi)存溢出”例驹。
垃圾回收的基本原理(算法思路都是一致的:把所有對象組成一個集合捐韩,或可以理解為樹狀結(jié)構(gòu),從樹根開始找眠饮,只要可以找到的都是活動對象奥帘,如果找不到,這個對象就被回收了)
垃圾回收分為兩個階段:
標記 --> 壓縮標記的過程仪召,其實就是判斷對象是否可達的過程。當所有的根都檢查完畢后松蒜,堆中將包含可達(已標記)與不可達(未標記)對象扔茅。標記完成后,進入壓縮階段秸苗。在這個階段中召娜,垃圾回收器線性的遍歷堆,以尋找不可達對象的連續(xù)內(nèi)存塊惊楼。并把可達對象移動到這里以節(jié)約內(nèi)存空間玖瘸。
垃圾收集算法
Mark-Sweep標記清理算法
階段1: Mark-Sweep 標記清除階段,先假設heap中所有對象都可以回收檀咙,然后找出不能回收的對象雅倒,給這些對象打上標記,最后heap中沒有打標記的對象都是可以被回收的弧可;
階段2: Compact 壓縮階段蔑匣,對象回收之后heap內(nèi)存空間變得不連續(xù),在heap中移動這些對象棕诵,使他們重新從heap基地址開始連續(xù)排列(節(jié)省內(nèi)存資源)裁良。
Heap內(nèi)存經(jīng)過回收、壓縮之后校套,可以繼續(xù)采用前面的heap內(nèi)存分配方法价脾, 即僅用一個指針記錄heap分配的起始地址就可以。主要處理步驟:將線程掛起→確定roots→創(chuàng)建reachable objects graph→對象回收→heap壓縮→指針修復笛匙∏劝眩可以這樣理解roots:heap中對象的引用關(guān)系錯綜復雜(交叉引用、循環(huán)引用)膳算,形成復雜的 graph座硕,roots是CLR在heap之外可以找到的各種入口點。
GC搜索roots的地方包括全局對象涕蜂、靜態(tài)變量华匾、局部對象、函數(shù)調(diào)用參數(shù)、當前CPU寄存器中的對象指針(還有finalization queue)等蜘拉。主要可以歸為2種類型:已經(jīng)初始化了的靜態(tài)變量萨西、線程仍在使用的對象(stack+CPU register)
指針修復是因為compact過程移動了heap對象,對象地址發(fā)生變化旭旭,需要修復所有引用指針谎脯,包括stack、CPU register中的指針以及heap中其他對象的引用指針持寄。
復制算法
新生代的內(nèi)存被劃分為一塊較大的Eden空間和兩塊較小的Survivor空間源梭,每次使用Eden和其中一塊Survivor。每次回收時稍味,將Eden和Survivor中還存活著的對象一次性復制到另外一塊Survivor空間上废麻,最后清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認Eden區(qū)和Survivor區(qū)的比例為8:1模庐,意思是每次新生代中可用內(nèi)存空間為整個新生代容量的90%烛愧。當然,我們沒有辦法保證每次回收都只有不多于10%的對象存活掂碱,當Survivor空間不夠用時怜姿,需要依賴老年代進行分配擔保(Handle Promotion)。
標記整理算法
與標記清理算法過程一樣疼燥,只是不直接清理可回收對象沧卢,而是將所有存活對象移動到一端,之后清理邊界之外的對象內(nèi)存
分代收集算法
?現(xiàn)代商用虛擬機基本都采用分代收集算法來進行垃圾回收悴了。這種算法沒什么特別的搏恤,無非是上面內(nèi)容的結(jié)合罷了,根據(jù)對象的生命周期的不同將內(nèi)存劃分為幾塊湃交,然后根據(jù)各塊的特點采用最適當?shù)氖占惴ㄊ炜铡4笈鷮ο笏廊ァ⑸倭繉ο蟠婊畹模ㄐ律└爿海褂脧椭扑惴ㄏ⒙蓿瑥椭瞥杀镜停粚ο蟠婊盥矢卟挪住]有額外空間進行分配擔保的(老年代)迈喉,采用標記-清理算法或者標記-整理算法。
?
?
?
如何找到需要回收的對象
1温圆、引用計數(shù)法:給對象中添加一個引用計數(shù)器挨摸,每當一個地方引用這個對象時,計數(shù)器值+1岁歉;當引用失效時得运,計數(shù)器值-1。任何時刻計數(shù)值為0的對象就是不可能再被使用的。
2熔掺、可達性分析法:對于可達性分析算法而言饱搏,未到達的對象并非是“非死不可”的,若要宣判一個對象死亡置逻,至少需要經(jīng)歷兩次標記階段推沸。
1. 如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GCRoots相連的引用鏈,則該對象被第一次標記并進行一次篩選券坞,篩選條件為是否有必要執(zhí)行該對象的finalize方法鬓催,若對象沒有覆蓋finalize方法或者該finalize方法已經(jīng)被虛擬機執(zhí)行過了(?finalize()在什么時候被調(diào)用??有三種情況??????1.所有對象被Garbage Collection時自動調(diào)用,比如運行System.gc()的時候.??????2.程序退出時為每個對象調(diào)用一次finalize方法。??????3.顯式的調(diào)用finalize方法)恨锚,則均視作不必要執(zhí)行該對象的finalize方法深浮,即該對象將會被回收。反之眠冈,若對象覆蓋了finalize方法并且該finalize方法并沒有被執(zhí)行過,那么菌瘫,這個對象會被放置在一個叫F-Queue的隊列中蜗顽,之后會由虛擬機自動建立的、優(yōu)先級低的Finalizer線程去執(zhí)行雨让,而虛擬機不必要等待該線程執(zhí)行結(jié)束雇盖,即虛擬機只負責建立線程,其他的事情交給此線程去處理栖忠。
2.對F-Queue中對象進行第二次標記崔挖,如果對象在finalize方法中拯救了自己,即關(guān)聯(lián)上了GCRoots引用鏈庵寞,如把this關(guān)鍵字賦值給其他變量狸相,那么在第二次標記的時候該對象將從“即將回收”的集合中移除,如果對象還是沒有拯救自己捐川,那就會被回收脓鹃。它只能拯救自己一次,第二次就被回收了古沥。此外瘸右,從我們可以得知,一個堆對象的this引用會永遠存在岩齿,在方法體內(nèi)可以將this引用賦值給其他變量太颤,這樣堆中對象就可以被其他變量所引用,即不會被回收.
Java有了GC同樣會出現(xiàn)內(nèi)存泄露問題
1.靜態(tài)集合類像HashMap盹沈、Vector等的使用最容易出現(xiàn)內(nèi)存泄露龄章,這些靜態(tài)變量的生命周期和應用程序一致,所有的對象Object也不能被釋放,因為他們也將一直被Vector等應用著瓦堵。
2.各種連接基协,數(shù)據(jù)庫連接,網(wǎng)絡連接菇用,IO連接等沒有顯示調(diào)用close關(guān)閉澜驮,不被GC回收導致內(nèi)存泄露。
3.監(jiān)聽器的使用惋鸥,在釋放對象的同時沒有相應刪除監(jiān)聽器的時候也可能導致內(nèi)存泄露杂穷。
垃圾回收器負責回收所有無任何引用對象的內(nèi)存空間。
注意:垃圾回收回收的是無任何引用的對象占據(jù)的內(nèi)存空間而不是對象本身卦绣。
GC注意事項:
1耐量、只管理內(nèi)存,非托管資源滤港,如文件句柄廊蜒,GDI資源,數(shù)據(jù)庫連接等還需要用戶去管理溅漾。
2山叮、循環(huán)引用,網(wǎng)狀結(jié)構(gòu)等的實現(xiàn)會變得簡單添履。GC的標志-壓縮算法能有效的檢測這些關(guān)系屁倔,并將不再被引用的網(wǎng)狀結(jié)構(gòu)整體刪除。
3暮胧、GC通過從程序的根對象開始遍歷來檢測一個對象是否可被其他對象訪問锐借,而不是用類似于COM中的引用計數(shù)方法。
4往衷、GC在一個獨立的線程中運行來刪除不再被引用的內(nèi)存钞翔。
5、GC每次運行時會壓縮托管堆炼绘。
GC總結(jié)
[if !supportLists]1.?[endif]JVM堆的大小決定了GC的運行時間嗅战。如果JVM堆的大小超過一定的限度,那么GC的運行時間會很長俺亮。2.對象生存的時間越長驮捍,GC需要的回收時間也越長,影響了回收速度脚曾。3.大多數(shù)對象都是短命的东且,所以,如果能讓這些對象的生存期在GC的一次運行周期內(nèi)本讥,wonderful珊泳!4.應用程序中鲁冯,建立與釋放對象的速度決定了垃圾收集的頻率。5.如果GC一次運行周期超過3-5秒色查,這會很影響應用程序的運行薯演,如果可以,應該減少JVM堆的大小了秧了。6.前輩經(jīng)驗之談:通常情況下跨扮,JVM堆的大小應為物理內(nèi)存的80%。