參考
新生代老生代
垃圾回收算法參考
優(yōu)化參考
面向GC編程
Java 堆內(nèi)存
面試GC問答
一、算法
引用計數(shù)法:一個對象被引用計數(shù)器加一,取消引用計數(shù)器減一友驮,引用計數(shù)器為0才能被回收疼鸟。優(yōu)點:簡單殴蓬。缺點:不能解決循環(huán)引用的問題立润,比如A引用B狂窑,B引用A,但是這兩個對象沒有被其他任何對象引用桑腮,屬于垃圾對象泉哈,卻不能回收;每次引用都會附件一個加減法破讨,影響性能丛晦。
引用計數(shù)的方法由于存在顯著的缺點,實際上并未被JVM所使用提陶。標(biāo)記清除法:分為兩個階段:標(biāo)記階段和清除階段烫沙。標(biāo)記階段通過根節(jié)點標(biāo)記所有可達(dá)對象,清除階段清除所有不可達(dá)對象隙笆。缺點:因為清除不可達(dá)對象之后剩余的內(nèi)存不連續(xù)斧吐,會產(chǎn)生大量內(nèi)存碎片,不利于大對象的分配仲器。由于無法找到足夠的連續(xù)內(nèi)存煤率,不得不觸發(fā)另一次GC。
可以做根節(jié)點的對象:虛擬機(jī)棧(棧幀中的本地變量表)中的引用的對象
方法區(qū)中的類靜態(tài)屬性引用的對象
方法區(qū)中的常量引用的對象
本地方法棧中JNI(Native方法)的引用對象
復(fù)制算法:將內(nèi)存空間分成相同的兩塊乏冀,每次只是用其中的一塊蝶糯,垃圾回收時,將正在使用的內(nèi)存中的存活對象復(fù)制到另外一塊空間辆沦,然后清除正在使用的內(nèi)存空間中的所有對象昼捍,這種回收算法適用于新生代垃圾回收。優(yōu)點:垃圾回收對象比較多時需要復(fù)制的對象很少肢扯,性能較好妒茬;不會存在內(nèi)存碎片。缺點:將系統(tǒng)內(nèi)存折半蔚晨。
標(biāo)記壓縮算法:是一種老年代回收算法乍钻,在標(biāo)記清除的基礎(chǔ)上做了一些優(yōu)化,首先從根節(jié)點開始標(biāo)記所有不可達(dá)的對象铭腕,然后將所有可達(dá)的對象移動到內(nèi)存的一端银择,最后清除所有不可達(dá)的對象。優(yōu)點:不用將內(nèi)存分為兩塊累舷;不會產(chǎn)生內(nèi)存碎片浩考。
分代算法:新生代使用復(fù)制算法,老生代使用標(biāo)記清除算法或者標(biāo)記壓縮算法被盈。幾乎所有的垃圾回收期都區(qū)分新生代和老生帶析孽。
分區(qū)算法:將整個堆空間分成很多個連續(xù)的不同的小空間搭伤,每個小空間獨立使用,獨立回收袜瞬。為了更好的控制gc停頓時間怜俐,可以根據(jù)目標(biāo)停頓時間合理地回收若干個小區(qū)間,而不是整個堆空間吞滞,從而減少gc停頓時間佑菩。
二盾沫、新生代young generation(執(zhí)行minor gc 復(fù)制算法)和老年代old generation(執(zhí)行major gc或叫full gc標(biāo)記清除算法)
為什么要分代裁赠?
總有一些對象是長期使用的,如果每次GC都掃描他們赴精,然后發(fā)現(xiàn)無法回收佩捞,這樣效率就很差。所以就把對象增加了一個年齡屬性蕾哟,這樣如果每次GC都回收不掉他們一忱,他們的年齡就增加1,慢慢地就變成老年人了谭确。達(dá)到一定年齡后帘营,就要分區(qū)了。老年人會離開新生代區(qū)域逐哈,進(jìn)入老年代區(qū)域芬迄。這樣常規(guī)的GC只掃描新生代區(qū)域,處于老年代區(qū)域的對象就不會被頻繁地掃描了昂秃。
80%的對象都是朝生夕死禀梳,對于負(fù)載不高的應(yīng)用,可能數(shù)月都不會發(fā)生FullGC肠骆。新生代再細(xì)分算途,有三個區(qū)域:Eden區(qū),即伊甸園區(qū)蚀腿。還有2個Survivor區(qū)嘴瓤,即幸存者區(qū),分別叫from和to莉钙。from和to這兩塊就是復(fù)制算法中提到的兩塊內(nèi)存纱注,它們互相復(fù)制來復(fù)制去,互相換著當(dāng)from和to.當(dāng)然其中有一個肯定是空的胆胰。當(dāng)eden滿時狞贱,minorgc開始,Eden區(qū)也會執(zhí)行復(fù)制算法蜀涨,把所有存活對象全部復(fù)制到to區(qū)域瞎嬉。from區(qū)域的對象會根據(jù)年齡決定去向蝎毡,達(dá)到要求的就去老年代,沒達(dá)到要求的也會進(jìn)入to區(qū)域氧枣。然后沐兵,Eden和from區(qū)都會被清空。當(dāng)to區(qū)域也被塞滿時便监,所有對象都會被復(fù)制到老年區(qū)扎谎。
一個對象的一輩子:我是一個普通的Java對象,我出生在Eden區(qū)烧董,在Eden區(qū)我還看到和我長的很像的小兄弟毁靶,我們在Eden區(qū)中玩了挺長時間。有一天Eden區(qū)中的人實在是太多了逊移,我就被迫去了Survivor區(qū)的“From”區(qū)预吆,自從去了Survivor區(qū),我就開始漂了胳泉,有時候在Survivor的“From”區(qū)拐叉,有時候在Survivor的“To”區(qū),居無定所扇商。直到我18歲的時候凤瘦,爸爸說我成人了,該去社會上闖闖了案铺。于是我就去了年老代那邊蔬芥,年老代里义锥,人很多筋量,并且年齡都挺大的,我在這里也認(rèn)識了很多人镣衡。在年老代里暇番,我生活了20年(每次GC加一歲)嗤放,然后被回收。
新生代98%的對象都是朝生夕死壁酬,虛擬機(jī)默認(rèn)eden和survivor大小比例是8次酌;1,也就是每次只有10%的內(nèi)存空間是作為to區(qū)域被浪費舆乔,而不是傳統(tǒng)復(fù)制算法中所說的浪費一半內(nèi)存岳服。
三、面向GC編程優(yōu)化
- 減少創(chuàng)建對象的數(shù)量希俩,比如使用StringBuilder代替string
- 老年代的GC很耗時吊宋,要減少進(jìn)入老年代的對象數(shù)量。比如對象池颜武,因為對象長期存活璃搜,會晉升到老年代拖吼,影響GC效率。另外對象池通常涉及并發(fā)訪問这吻,處理同步帶來的開銷也很大吊档。而重新創(chuàng)建一個對象的開銷,可能比這兩者都要小唾糯。
- 在一個非常大的方法體內(nèi)怠硼,對于較大的對象,將其引用置為null移怯,某種程度可以幫助GC香璃。但大部分情況下這種行為意義不大。
- 一些基于數(shù)組的數(shù)據(jù)結(jié)構(gòu)芋酌,例如StringBuilder增显、StringBuffer雁佳、ArrayList脐帝、HashMap等等,在擴(kuò)容的時候都需要做ArrayCopy糖权,對于不斷增長的結(jié)構(gòu)來說堵腹,經(jīng)過若干次擴(kuò)容,會存在大量無用的老數(shù)組星澳,而回收這些數(shù)組的壓力疚顷,全都會加在GC身上。這些容器的構(gòu)造函數(shù)中通常都有一個可以指定大小的參數(shù)禁偎,如果對于某些大小可以預(yù)估的容器腿堤,建議加上這個參數(shù)。
- 盡可能縮小對象的作用域如暖,即生命周期笆檀。如果可以在方法內(nèi)聲明的局部變量,就不要聲明為實例變量盒至。除非你的對象是單例的或不變的酗洒,否則盡可能少地聲明static變量。