4 GC的流程是怎么樣的舍咖?介紹下GC回收機制與分代回收策略
這道題想考察什么澡罚?
Java基礎(chǔ)掌握情況嵌溢,掌握對象回收過程以避免開發(fā)時出現(xiàn)內(nèi)存問題
考察的知識點
GC機制
考生如何回答
說到GC垃圾回收,首先要知道什么是“垃圾”话浇,垃圾就是沒有用的對象扩灯,那么怎樣判定一個對象是不是垃圾(能不能被回收)媚赖?Java 虛擬機中使用一種叫作可達性分析的算法來決定對象是否可以被回收霜瘪。
可達性分析
可達性分析就通過一組名為”GC Root"的對象作為起始點,從這些節(jié)點開始向下搜索惧磺,搜索所走過的路徑稱為引用鏈颖对,最后通過判斷對象的引用鏈?zhǔn)欠窨蛇_來決定對象是否可以被回收。
GC Root指的是:
- Java 虛擬機棧(局部變量表)中的引用的對象磨隘。也就是正在運行的方法中的局部變量所引用的對象
- 方法區(qū)中靜態(tài)引用指向的對象缤底。也就是類中的static修飾的變量所引用的對象
- 方法區(qū)中常量引用的對象。
- 仍處于存活狀態(tài)中的線程對象番捂。
- Native 方法中 JNI 引用的對象个唧。
優(yōu)點
可達性分析可以解決引用計數(shù)器所不能解決的循環(huán)引用問題。即便對象a和b相互引用白嘁,只要從GC Roots出發(fā)無法到達a或者b坑鱼,那么可達性分析便不會將它們加入存活對象合集之中膘流。
缺點
在多線程環(huán)境下絮缅,其他線程可能會更新已經(jīng)訪問過的對象中的引用,從而造成誤報(將引用設(shè)置為null)或者漏報(將引用設(shè)置為未被訪問過的對象)呼股。誤報并沒有什么傷害耕魄,Java虛擬機至多損失了部分垃圾回收的機會。漏報則比較麻煩彭谁,因為垃圾回收器可能回收事實上仍被引用的對象內(nèi)存吸奴。 一旦從原引用訪問已經(jīng)被回收了的對象,則很有可能會直接導(dǎo)致Java虛擬機崩潰缠局。
垃圾回收算法
在標(biāo)記出對象是否可被回收后则奥,接下來就需要對可回收對象進行回收∠猎埃基本的回收算法有:標(biāo)記-清理读处、標(biāo)記-整理與復(fù)制算法。
標(biāo)記清除算法
從”GC Roots”集合開始唱矛,將內(nèi)存整個遍歷一次罚舱,保留所有可以被 GC Roots 直接或間接引用到的對象,而剩下的對象都當(dāng)作垃圾對待并回收绎谦,過程分為 標(biāo)記 和 清除 兩個步驟管闷。
- 優(yōu)點:實現(xiàn)簡單,不需要將對象進行移動窃肠。
- 缺點:這個算法需要中斷進程內(nèi)其他組件的執(zhí)行(stop the world)包个,并且可能產(chǎn)生內(nèi)存碎片,提高了垃圾回收的頻率冤留。
標(biāo)記整理算法
與標(biāo)記-清除不同的是它并不簡單地清理未標(biāo)記的對象碧囊,而是將所有的存活對象壓縮到內(nèi)存的一端恃锉。最后,清理邊界外所有的空間呕臂。
- 優(yōu)點:這種方法既避免了碎片的產(chǎn)生破托,又不需要兩塊相同的內(nèi)存空間,因此歧蒋,其性價比比較高土砂。
- 缺點:所謂壓縮操作,仍需要進行局部對象移動谜洽,所以一定程度上還是降低了效率萝映。
復(fù)制算法
將現(xiàn)有的內(nèi)存空間分為兩快,每次只使用其中一塊阐虚,在垃圾回收時將正在使用的內(nèi)存中的存活對象復(fù)制到未被使用的內(nèi)存塊中序臂。之后,清除正在使用的內(nèi)存塊中的所有對象实束,交換兩個內(nèi)存的角色奥秆,完成垃圾回收。
- 優(yōu)點:按順序分配內(nèi)存即可咸灿,實現(xiàn)簡單构订、運行高效,不用考慮內(nèi)存碎片避矢。
- 缺點:可用的內(nèi)存大小縮小為原來的一半悼瘾,對象存活率高時會頻繁進行復(fù)制。
分代回收策略
不同的垃圾收集器實現(xiàn)采用不同的算法進行垃圾回收审胸,除此之外現(xiàn)代虛擬機還會采用分代機制來進行垃圾回收亥宿,根據(jù)對象存活的周期不同,把堆內(nèi)存劃分為不同區(qū)域砂沛,不同區(qū)域采用不同算法進行垃圾回收烫扼。
分代的垃圾回收策略,是基于這樣一個事實:不同的對象的生命周期是不一樣的尺上。因此材蛛,不同生命周期的對象可以采取不同的收集方式,以便提高回收效率怎抛。
在Java程序運行的過程中卑吭,會產(chǎn)生大量的對象,其中有些對象是與業(yè)務(wù)信息相關(guān)马绝,比如Http請求中的Session對象豆赏、線程、Socket連接,這類對象跟業(yè)務(wù)直接掛鉤掷邦,因此生命周期比較長白胀。但是還有一些對象,主要是程序運行過程中生成的臨時變量抚岗,這些對象生命周期會比較短或杠,比如:String對象,由于其不變類的特性宣蔚,系統(tǒng)會產(chǎn)生大量的這些對象向抢,有些對象甚至只用一次即可回收。
試想胚委,在不進行對象存活時間區(qū)分的情況下挟鸠,每次垃圾回收都是對整個堆空間進行回收,花費時間相對會長亩冬,同時艘希,因為每次回收都需要遍歷所有存活對象,但實際上硅急,對于生命周期長的對象而言覆享,這種遍歷是沒有效果的,因為可能進行了很多次遍歷铜秆,但是他們依舊存在淹真。因此,分代垃圾回收采用分治的思想连茧,進行代的劃分,把不同生命周期的對象放在不同代上巍糯,不同代上采用最適合它的垃圾回收方式進行回收啸驯。
代際劃分
堆內(nèi)存分為年輕代(Young Generation)和老年代(Old Generation)。而持久代使用非堆內(nèi)存祟峦,主要用于存儲一些類的元數(shù)據(jù)罚斗,常量池,java類宅楞,靜態(tài)文件等信息针姿。
垃圾回收
年輕代會劃分出Eden區(qū)域與兩個大小對等的Survivor區(qū)域。 其比例一般為8:1:1厌衙,這是因為根據(jù)統(tǒng)計95%的對象朝生夕死距淫,存活時間極短。
- 新生成的對象優(yōu)先存放在新生代中
- 存活率很低婶希,回收效率很高
- 一般采用的 GC 回收算法是復(fù)制算法
當(dāng)新對象生成榕暇,并且在Eden申請空間失敗時,就會觸發(fā)GC,對Eden區(qū)域進行GC彤枢,清除非存活對象狰晚,并且把尚且存活的對象移動到Survivor區(qū),然后整理Survivor的兩個區(qū)缴啡。這種方式的GC是對年輕代的Eden區(qū)進行壁晒,不會影響到年老代。因為大部分對象都是從Eden區(qū)開始的业栅,同時Eden區(qū)不會分配的很大讨衣,所以Eden區(qū)的GC會頻繁進行。所以一般在這里需要使用速度快式镐、效率高的算法反镇,使Eden區(qū)能盡快空閑出來。
minor gc
新對象的內(nèi)存分配都是先在Eden區(qū)域中進行的娘汞,當(dāng)Eden區(qū)域的空間不足于分配新對象時歹茶,就會觸發(fā)年輕代上的垃圾回收,我們稱之為"minor gc"你弦。同時惊豺,每個對象都有一個“年齡”,這個年齡實際上指的就是該對象經(jīng)歷過的minor gc的次數(shù)禽作。如圖1所示尸昧,當(dāng)對象剛分配到Eden區(qū)域時,對象的年齡為“0”旷偿,當(dāng)minor gc被觸發(fā)后烹俗,所有存活的對象(仍然可達對象)會被拷貝到其中一個Survivor區(qū)域,同時年齡增長為“1”萍程。并清除整個Eden內(nèi)存區(qū)域中的非可達對象幢妄。
當(dāng)?shù)诙蝝inor gc被觸發(fā)時,JVM會通過Mark算法找出所有在Eden內(nèi)存區(qū)域和Survivor1內(nèi)存區(qū)域存活的對象茫负,并將他們拷貝到新的Survivor2內(nèi)存區(qū)域(這也就是為什么需要兩個大小一樣的Survivor區(qū)域的原因)蕉鸳,同時對象的年齡加1. 最后,清除所有在Eden內(nèi)存區(qū)域和Survivor1內(nèi)存區(qū)域的非可達對象忍法。
當(dāng)對象的年齡足夠大(年齡可以通過JVM參數(shù)進行指定潮尝,默認(rèn)為15歲,CMS收集器默認(rèn)6歲饿序,不同的垃圾收集器會略微有點不同 )勉失,當(dāng)minor gc再次發(fā)生時,它會從Survivor內(nèi)存區(qū)域中升級到年老代中嗤堰,如圖3所示戴质。
major gc
當(dāng)minor gc發(fā)生時度宦,又有對象從Survivor區(qū)域升級到Tenured區(qū)域,但是Tenured區(qū)域已經(jīng)沒有空間容納新的對象了告匠,那么這個時候就會觸發(fā)年老代上的垃圾回收戈抄,我們稱之為"major gc"。而在年老代上選擇的垃圾回收算法則取決于JVM上采用的是什么垃圾回收器后专。
總結(jié)
在JVM中一般采用可達性分析法進行是否可回收的判定划鸽,確定對象需要被回收后,對象在哪個代際將會采用不同的垃圾回收算法進行回收戚哎,這些算法包括:標(biāo)記-清除裸诽,標(biāo)記-整理與復(fù)制算法。
而之所以采用分代策略的原因是:不同的對象的生命周期是不一樣的型凳。因此丈冬,不同生命周期的對象可以采取不同的收集方式,以便提高回收效率甘畅。 如果每次垃圾回收都是對整個堆空間進行回收埂蕊,花費時間相對會長,而對于生命周期長的對象而言疏唾,這種遍歷是沒有效果的蓄氧,因為可能進行了很多次遍歷,但是他們依舊存在槐脏。
5 Java中對象如何晉升到老年代喉童?
這道題想考察什么?
Java基礎(chǔ)掌握情況顿天,掌握對象回收過程以避免開發(fā)時出現(xiàn)內(nèi)存問題
考察的知識點
GC機制
考生如何回答
新對象的內(nèi)存分配都是先在Eden區(qū)域中進行的堂氯,當(dāng)Eden區(qū)域的空間不足于分配新對象時,就會觸發(fā)年輕代上的垃圾回收露氮,我們稱之為"minor gc"祖灰。同時,每個對象都有一個“年齡”畔规,這個年齡實際上指的就是該對象經(jīng)歷過的minor gc的次數(shù)。當(dāng)對象的年齡足夠大恨统,當(dāng)minor gc再次發(fā)生時叁扫,它會從Survivor內(nèi)存區(qū)域中升級到年老代中。
因此對象晉升老年代的條件之一為:若年齡超過一定限制(如15)畜埋,則被晉升到老年態(tài)莫绣。即長期存活的對象進入老年代。除此之外悠鞍,以下情況都會導(dǎo)致對象晉升老年代:
-
大對象直接進入老年代
多大由JVM參數(shù)
-XX:PretenureSizeThreshold=x
決定对室; -
動態(tài)對象年齡判定
當(dāng) Survivor 空間中相同年齡所有對象的大小總和大于 Survivor 空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,而不需要達到默認(rèn)的分代年齡掩宜。
除了以上提到的幾種情況外蔫骂,其實還有一種可能導(dǎo)致對象晉升老年代:分配擔(dān)保機制。
空間分配擔(dān)保
在發(fā)生Minor GC之前牺汤,虛擬機會檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象的總空間辽旋,
如果大于,則此次Minor GC是安全的
如果小于檐迟,則虛擬機會查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗补胚。如果HandlePromotionFailure=true,那么會繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次晉升到老年代的對象的平均大小追迟,如果大于溶其,則嘗試進行一次Minor GC,但這次Minor GC依然是有風(fēng)險的敦间;如果小于或者HandlePromotionFailure=false瓶逃,則改為進行一次Full GC。
為什么要進行空間擔(dān)保每瞒?
新生代一般采用復(fù)制收集算法金闽,假如大量對象在Minor GC后仍然存活(最極端情況為內(nèi)存回收后新生代中所有對象均存活),而Survivor空間是比較小的剿骨,這時就需要老年代進行分配擔(dān)保代芜,把Survivor無法容納的對象放到老年代。老年代要進行空間分配擔(dān)保浓利,前提是老年代得有足夠空間來容納這些對象挤庇,但一共有多少對象在內(nèi)存回收后存活下來是不可預(yù)知的,因此只好取之前每次垃圾回收后晉升到老年代的對象大小的平均值作為參考贷掖。使用這個平均值與老年代剩余空間進行比較嫡秕,來決定是否進行Full GC來讓老年代騰出更多空間。
Minor Gc后的對象太多無法放入Survivor區(qū)怎么辦?
假如在發(fā)生gc的時候苹威,eden區(qū)里有150MB對象存活昆咽,而Survivor區(qū)只有100MB,無法全部放入牙甫,這時就必須把這些對象直接轉(zhuǎn)移到老年代里掷酗。
如果Minor gc后新生代的對象都存活下來,然后需要全部轉(zhuǎn)移到老年代窟哺,但是老年代空間不夠泻轰,怎么辦?
這時如果設(shè)置了 "-XX:-HandlePromotionFailure"的參數(shù)且轨,就會嘗試判斷浮声,看老年代內(nèi)存大小是否大于之前每一次Minor gc后進入老年代的對象的平均大小虚婿。比如說,之前Minor gc 平均10M左右的對象進入老年代泳挥,此時老年代可用內(nèi)存大于10MB,那么大概率老年代空間是足夠的然痊。
1、如果判斷老年代空間不夠羡洁,或者是根本沒設(shè)置這個參數(shù)玷过,那就直接觸發(fā)"Full GC(對整個堆回收,包含:年輕代筑煮、老年代)"辛蚊,對老年代進行垃圾回收,騰出空間真仲。
2袋马、如果判斷老年代空間足夠,就冒風(fēng)險嘗試Minor gc秸应。這時有以下幾種可能虑凛。
- Minor Gc 后,剩余的存活對象大小软啼,小于Survivor區(qū)桑谍,那就直接進入Survivor區(qū)。
- MInor Gc 后祸挪,剩余的存活對象大小锣披,大于Survivor區(qū),小于老年代可用內(nèi)存贿条,那就直接去老年代雹仿。
- Minor Gc后,大于Survivor整以,老年代胧辽,很不幸,就會發(fā)生"Handle Promotion Failure"的情況 公黑,觸發(fā)"Full GC"邑商。
如果 Full gc后老年代還是沒有足夠的空間存放剩余的存活對象,那么就會導(dǎo)致“OOM” 內(nèi)存溢出凡蚜。
總結(jié)
實際上有四種情況可能會導(dǎo)致對象晉升老年代:
- 大對象直接進入老年代
- 年齡超過閾值
- 動態(tài)對象年齡判定
- 年輕代空間不足
6 判斷對象是否被回收奠骄,有哪些GC算法,虛擬機使用最多的是什么算法番刊?(美團)
這道題想考察什么?
是否掌握可達性分析法了解如何確定對象是否可被回收影锈,從而避免程序內(nèi)存泄露問題
考察的知識點
GC機制芹务、可達性分析發(fā)蝉绷、引用計數(shù)
考生如何回答
Java利用GC機制讓開發(fā)者不必再像C/C++手動回收內(nèi)存,但是并不是有了GC機制就萬事皆可枣抱,在不了解GC機制算法的情況下熔吗,很容易出現(xiàn)代碼問題導(dǎo)致該回收的對象無法被回收(內(nèi)存泄漏),一直占用內(nèi)存佳晶,導(dǎo)致程序可用內(nèi)存越來越少桅狠,最終出現(xiàn)OOM。
首先GC會幫助我們自動回收內(nèi)存轿秧,那么GC是如何確定對象是否可被回收的中跌?在《5.4 介紹下GC回收機制與分代回收策略》中介紹了可達性分析法。而除了可達性分析法之外菇篡,還有被淘汰的引用計數(shù)算法:
給對象中添加一個引用計數(shù)器漩符,每當(dāng)有一個地方引用它時,計數(shù)器值就加1驱还;當(dāng)引用失效時嗜暴,計數(shù)器值就減1;任何時刻計數(shù)器為0的對象就是不可能再被使用的议蟆。
目前主流的java虛擬機都摒棄掉了這種算法闷沥,最主要的原因是它很難解決對象之間相互循環(huán)引用的問題,盡管該算法執(zhí)行效率很高咐容。比如:
class A{
B b;
}
class B{
A a;
}
A a = new A();
B b = new B();
//循環(huán)引用 導(dǎo)致無法釋放
a.b = b;
b.a = a;
因此實際現(xiàn)在Java虛擬機都是采用的可達性分析法舆逃。