Java架構師JVM垃圾回收算法飞蚓,程序員必看B烁邸!趴拧!
談到Java編程溅漾,那就不得不談GC,談到GC不得不談垃圾回收算法
對象已死嗎
在進行垃圾回收之前,第一件事就是判斷哪些對象還存活著著榴,哪些對象已死需要被回收添履。
1.引用計數(shù)算法
????????很多判斷對象是否存活的算法是這樣解答的:給對象中添加一個引用計數(shù)器,每當有個地方引用它時脑又,計數(shù)器值就加1暮胧;當引用失效時,計數(shù)器值就減1问麸,任何時刻計數(shù)器為0的對象就是不可能再被使用的往衷,該對象將被回收效诅。
????????客觀的說泣洞,引用計數(shù)法實現(xiàn)簡單,效率高奠蹬。但是沒有主流JVM選擇引用計數(shù)法管理內(nèi)存哮笆,因為他無法解決循環(huán)引用的問題俺亮。例如a.objB=b,b.objA=a,
此時對象a、b的計數(shù)器永遠至少為1疟呐,當兩個對象都不在使用時,并不會置為0东且,所以難以被回收启具。并且,引用計數(shù)器要求在每次因引用產(chǎn)生和消除的時候珊泳,伴隨一個加法操作和減法操作鲁冯,對系統(tǒng)性能會有一定的影響拷沸。
2.可達性分析算法
????????主流JVM都是稱通過可達性分析來判定對象是否存活的。這個算法的基本思路就是通過一系列的稱為"GC Roots"的對象作為起始點薯演,從這些節(jié)點開始向下搜索撞芍,搜索所走過的路徑稱為引用鏈( Reference Chain),當一個對象到GC Roots 沒有任何引用鏈相連跨扮,用圖論的話來說序无,就是從GC Roots 到這個對象不可達) 時,則證明此對象是不可用的衡创。如圖所示帝嗡,對象object 5、object 6璃氢、object 7 雖然互相有關聯(lián)哟玷,但是它們到GC Roots 是不可達的,所以它們將會被判定為是可回收的對象一也。
垃圾回收算法
1.引用技術算法
????????前面已經(jīng)介紹
2.標記-清除算法
????????最基礎的收集算法是“標記- 清除”(Mark-Sweep) 算法巢寡,如同它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象椰苟,在標記完成后統(tǒng)一回收所有被標記的對象(可達性分析)抑月。之所以說它是最基礎的收集算法,是因為后續(xù)的收集算法都是基于這種思路并對其不足進行改進而得到的尊剔。
????????不足:效率問題爪幻,標記和清除兩個過程的效率都不高; 空間問題,標記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片须误,空間碎片太多可能會導致以后在程序運行過程中需要分配較大對象時挨稿,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。
3.標記-整理算法(標記-壓縮)
????????算法分為“標記”京痢、“壓縮”和“清除”三個階段:首先標記出所有需要回收的對象奶甘,把所有存活的對象壓縮到一段,然后清理掉端邊界以外的內(nèi)存祭椰。這樣
將不會產(chǎn)生磁盤碎片臭家。但是,壓縮階段占用了系統(tǒng)的消耗方淤,并且如果標記對象過多的話钉赁,損耗可能會很大,在標記對象相對較少的時候携茂,效率較高你踩。
4.復制算法
????????為了解決效率問題,一種稱為“復制”(Copying)? 的收集算法出現(xiàn)了,它將可用內(nèi)存按容量劃分為大小相等的兩塊带膜,每次只使用其中的一塊吩谦。當這一塊的內(nèi)存用完了,就將還存話著的對象復制到另外一塊上面膝藕,然后再把已使用過的內(nèi)存空間一次清理掉式廷。這樣使得每次都是對整個半?yún)^(qū)進行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復雜情況芭挽,只要移動堆頂指針滑废,按順序分配內(nèi)存即可,實現(xiàn)簡單览绿,運行高效策严。只是這種算法的代價是將內(nèi)存縮小為了原來的一半,未免太高了一點饿敲。
????????不足:對象較多時效率低妻导,并且有一半的空間浪費。
5.分代收集算法
? ? ? ?目前主流JVM垃圾收集都采用“分代收集”(Generational Collection) 算法怀各,這種算法并沒有什么新的思想倔韭,只是根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊。一般是把Java堆分為新生代和老年代瓢对,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴ㄊ僮谩T谛律校看卫占瘯r都發(fā)現(xiàn)有大批對象死去硕蛹,只有少量存活醇疼,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集法焰。而老年代中因為對象存活率高秧荆、沒有額外空間對它進行分配擔保(如果有空間,通過分配擔保機制進入老年代)埃仪,就必須使用“標記一清理”或者“標記一整理”算法來進行回收乙濒。
對于新生代和老年代來說,通常新生代回收的頻率很高卵蛉,但是每次回收的時間都很短颁股,而老年代回收的頻率比較低,但是被消耗很多的時間傻丝。為了支持高頻率的新生代回收甘有,虛擬機可能使用一種叫做卡表的數(shù)據(jù)結構,卡表為一個比特位集合葡缰,每一個比特位可以用來表示老年代的某一區(qū)域中的所有對象是否持有新生代對象的引用亏掀,
????????這樣以來允睹,新生代GC時,可以不用花大量時間掃描所有老年代對象幌氮,來確定每一個對象的引用關系,而可以先掃描卡表胁澳,只有當卡表的標記為1時该互,才需要掃描給定區(qū)域的老年代對象,而卡表為0的所在區(qū)域的老年代對象韭畸,一定不含有新生代對象的引用宇智。
????????卡表中每一位表示老年代4KB的空間,卡表記錄為0的老年代區(qū)域沒有任何對象指向新生代胰丁,只有卡表為1的區(qū)域才有對象包含新生代對象的引用随橘,因此在新生代GC時,只需要掃面卡表為1所在的老年代空間锦庸,使用這種方式机蔗,可以大大加快新生代的回收速度。
6.分區(qū)算法
????????分代算法將按照對象的生命周期長短劃分成兩個部分甘萧,分區(qū)算法將整個堆空間劃分成不同小區(qū)間萝嘁。每個小區(qū)間都獨立使用,獨立回收扬卷。這種算法的好處是可以控制一次回收多少個小區(qū)間牙言。
????????一般來說,在相同的條件下怪得,堆空間越大咱枉,一次GC時所需要的時間就越長,從而產(chǎn)生的停頓也越長徒恋。為了更好地控制GC產(chǎn)生的停頓時間蚕断,將一塊大的內(nèi)存區(qū)域分割為多個小塊,根據(jù)目標的停頓時間因谎,每次合理的回收若干個小區(qū)間基括,而不是整個堆空間,從而減少一次GC所產(chǎn)生的停頓财岔。
專注于Java架構師技術分享风皿,撩我免費送Java全套架構師晉級資料
(Java架構師交流*-*企*-*Q*-*鵝*-*裙*-*:445*-*820*-*908)