面試題一:判斷對(duì)象是否已死
判斷對(duì)象是否已死就是找出哪些對(duì)象是已經(jīng)死掉的,以后不會(huì)再用到的至非,就像地上有廢紙钠署、飲料瓶和百元大鈔,掃地前要先判斷出地上廢紙和飲料瓶是垃圾荒椭,百元大鈔不是垃圾谐鼎。判斷對(duì)象是否已死有引用計(jì)數(shù)算法和可達(dá)性分析算法。
1.引用計(jì)數(shù)算法
給每一個(gè)對(duì)象添加一個(gè)引用計(jì)數(shù)器戳杀,每當(dāng)有一個(gè)地方引用它時(shí)该面,計(jì)數(shù)器值加 1;每當(dāng)有一個(gè)地方不再引用它時(shí)信卡,計(jì)數(shù)器值減 1隔缀,這樣只要計(jì)數(shù)器的值不為 0,就說(shuō)明還有地方引用它傍菇,它就不是無(wú)用的對(duì)象猾瘸。如下圖,對(duì)象 2 有 1 個(gè)引用丢习,它的引用計(jì)數(shù)器值為 1牵触,對(duì)象 1有兩個(gè)地方引用,它的引用計(jì)數(shù)器值為 2 咐低。
這種方法看起來(lái)非常簡(jiǎn)單揽思,但目前許多主流的虛擬機(jī)都沒(méi)有選用這種算法來(lái)管理內(nèi)存,原因就是當(dāng)某些對(duì)象之間互相引用時(shí)见擦,無(wú)法判斷出這些對(duì)象是否已死钉汗,如下圖,對(duì)象 1 和對(duì)象 2 都沒(méi)有被堆外的變量引用鲤屡,而是被對(duì)方互相引用损痰,這時(shí)他們雖然沒(méi)有用處了,但是引用計(jì)數(shù)器的值仍然是 1酒来,無(wú)法判斷他們是死對(duì)象卢未,垃圾回收器也就無(wú)法回收。
2.可達(dá)性分析算法
了解可達(dá)性分析算法之前先了解一個(gè)概念——GC Roots堰汉,垃圾收集的起點(diǎn)辽社,可以作為 GC Roots 的有虛擬機(jī)棧中本地變量表中引用的對(duì)象、方法區(qū)中靜態(tài)屬性引用的對(duì)象翘鸭、方法區(qū)中常量引用的對(duì)象爹袁、本地方法棧中 JNI(Native 方法)引用的對(duì)象。
當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連(GC Roots 到這個(gè)對(duì)象不可達(dá))時(shí)矮固,就說(shuō)明此對(duì)象是不可用的失息,是死對(duì)象譬淳。
如下圖:object1、object2盹兢、object3邻梆、object4 和 GC Roots 之間有可達(dá)路徑,這些對(duì)象不會(huì)被回收绎秒,但 object5浦妄、object6、object7 到 GC Roots 之間沒(méi)有可達(dá)路徑见芹,這些對(duì)象就被判了死刑剂娄。
上面被判了死刑的對(duì)象(object5、object6玄呛、object7)并不是必死無(wú)疑阅懦,還有挽救的余地。進(jìn)行可達(dá)性分析后對(duì)象和 GC Roots 之間沒(méi)有引用鏈相連時(shí)徘铝,對(duì)象將會(huì)被進(jìn)行一次標(biāo)記耳胎,接著會(huì)判斷如果對(duì)象沒(méi)有覆蓋 Object的finalize() 方法或者 finalize() 方法已經(jīng)被虛擬機(jī)調(diào)用過(guò),那么它們就會(huì)被行刑(清除)惕它;如果對(duì)象覆蓋了 finalize() 方法且還沒(méi)有被調(diào)用怕午,則會(huì)執(zhí)行 finalize() 方法中的內(nèi)容,所以在 finalize() 方法中如果重新與 GC Roots 引用鏈上的對(duì)象關(guān)聯(lián)就可以拯救自己淹魄,但是一般不建議這么做郁惜,周志明老師也建議大家完全可以忘掉這個(gè)方法~
3.方法區(qū)回收
上面說(shuō)的都是對(duì)堆內(nèi)存中對(duì)象的判斷,方法區(qū)中主要回收的是廢棄的常量和無(wú)用的類甲锡。
判斷常量是否廢棄可以判斷是否有地方引用這個(gè)常量扳炬,如果沒(méi)有引用則為廢棄的常量。
判斷類是否廢棄需要同時(shí)滿足如下條件:
該類所有的實(shí)例已經(jīng)被回收(堆中不存在任何該類的實(shí)例)搔体。
加載該類的 ClassLoader 已經(jīng)被回收。
該類對(duì)應(yīng)的 java.lang.Class 對(duì)象在任何地方?jīng)]有被引用(無(wú)法通過(guò)反射訪問(wèn)該類的方法)半醉。
面試題二:常用四種垃圾回收算法
常用的垃圾回收算法有四種:標(biāo)記-清除算法疚俱、復(fù)制算法、標(biāo)記-整理算法缩多、分代收集算法呆奕。
1.標(biāo)記-清除算法
分為標(biāo)記和清除兩個(gè)階段,首先標(biāo)記出所有需要回收的對(duì)象衬吆,標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象梁钾,如下圖。
缺點(diǎn):標(biāo)記和清除兩個(gè)過(guò)程效率都不高逊抡;標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片姆泻。
2.復(fù)制算法
把內(nèi)存分為大小相等的兩塊零酪,每次存儲(chǔ)只用其中一塊,當(dāng)這一塊用完了拇勃,就把存活的對(duì)象全部復(fù)制到另一塊上四苇,同時(shí)把使用過(guò)的這塊內(nèi)存空間全部清理掉,往復(fù)循環(huán)方咆,如下圖月腋。
缺點(diǎn):實(shí)際可使用的內(nèi)存空間縮小為原來(lái)的一半,比較適合瓣赂。
3.標(biāo)記-整理算法
先對(duì)可用的對(duì)象進(jìn)行標(biāo)記榆骚,然后所有被標(biāo)記的對(duì)象向一段移動(dòng),最后清除可用對(duì)象邊界以外的內(nèi)存煌集,如下圖妓肢。
4.分代收集算法
把堆內(nèi)存分為新生代和老年代,新生代又分為 Eden 區(qū)牙勘、From Survivor 和 To Survivor职恳。一般新生代中的對(duì)象基本上都是朝生夕滅的,每次只有少量對(duì)象存活方面,因此采用復(fù)制算法放钦,只需要復(fù)制那些少量存活的對(duì)象就可以完成垃圾收集;老年代中的對(duì)象存活率較高恭金,就采用標(biāo)記-清除和標(biāo)記-整理算法來(lái)進(jìn)行回收操禀。
在這些區(qū)域的垃圾回收大概有如下幾種情況:
大多數(shù)情況下,新的對(duì)象都分配在Eden區(qū)横腿,當(dāng) Eden 區(qū)沒(méi)有空間進(jìn)行分配時(shí)颓屑,將進(jìn)行一次 Minor GC,清理 Eden 區(qū)中的無(wú)用對(duì)象耿焊。清理后揪惦,Eden 和 From Survivor 中的存活對(duì)象如果小于To Survivor 的可用空間則進(jìn)入To Survivor,否則直接進(jìn)入老年代)罗侯;Eden 和 From Survivor 中還存活且能夠進(jìn)入 To Survivor 的對(duì)象年齡增加 1 歲(虛擬機(jī)為每個(gè)對(duì)象定義了一個(gè)年齡計(jì)數(shù)器器腋,每執(zhí)行一次 Minor GC 年齡加 1),當(dāng)存活對(duì)象的年齡到達(dá)一定程度(默認(rèn) 15 歲)后進(jìn)入老年代钩杰,可以通過(guò) -XX:MaxTenuringThreshold 來(lái)設(shè)置年齡的值纫塌。
當(dāng)進(jìn)行了 Minor GC 后,Eden 還不足以為新對(duì)象分配空間(那這個(gè)新對(duì)象肯定很大)讲弄,新對(duì)象直接進(jìn)入老年代措左。
占 To Survivor 空間一半以上且年齡相等的對(duì)象,大于等于該年齡的對(duì)象直接進(jìn)入老年代避除,比如 Survivor 空間是 10M怎披,有幾個(gè)年齡為 4 的對(duì)象占用總空間已經(jīng)超過(guò) 5M胸嘁,則年齡大于等于 4 的對(duì)象都直接進(jìn)入老年代,不需要等到 MaxTenuringThreshold 指定的歲數(shù)钳枕。
在進(jìn)行 Minor GC 之前缴渊,會(huì)判斷老年代最大連續(xù)可用空間是否大于新生代所有對(duì)象總空間,如果大于鱼炒,說(shuō)明 Minor GC 是安全的衔沼,否則會(huì)判斷是否允許擔(dān)保失敗,如果允許昔瞧,判斷老年代最大連續(xù)可用空間是否大于歷次晉升到老年代的對(duì)象的平均大小指蚁,如果大于,則執(zhí)行 Minor GC自晰,否則執(zhí)行 Full GC凝化。
當(dāng)在 java 代碼里直接調(diào)用 System.gc() 時(shí),會(huì)建議 JVM 進(jìn)行 Full GC酬荞,但一般情況下都會(huì)觸發(fā) Full GC搓劫,一般不建議使用,盡量讓虛擬機(jī)自己管理 GC 的策略混巧。
永久代(方法區(qū))中用于存放類信息枪向,jdk1.6 及之前的版本永久代中還存儲(chǔ)常量、靜態(tài)變量等咧党,當(dāng)永久代的空間不足時(shí)秘蛔,也會(huì)觸發(fā) Full GC,如果經(jīng)過(guò) Full GC 還無(wú)法滿足永久代存放新數(shù)據(jù)的需求傍衡,就會(huì)拋出永久代的內(nèi)存溢出異常深员。
大對(duì)象(需要大量連續(xù)內(nèi)存的對(duì)象)例如很長(zhǎng)的數(shù)組,會(huì)直接進(jìn)入老年代蛙埂,如果老年代沒(méi)有足夠的連續(xù)大空間來(lái)存放倦畅,則會(huì)進(jìn)行 Full GC。
搜索公眾號(hào)【Java耕耘者】點(diǎn)擊小助理绣的,即可獲取大量?jī)?yōu)質(zhì)電子書(shū)和一份Java高級(jí)架構(gòu)資料叠赐、Spring源碼分析、Dubbo被辑、Redis、Netty敬惦、zookeeper盼理、Spring cloud、分布式等視頻資料