Java引用的種類
1.對(duì)象在內(nèi)存中的狀態(tài)
對(duì)于JVM的垃圾回收機(jī)制來說,是否回收一個(gè)對(duì)象的標(biāo)準(zhǔn)在于:是否還有引用變量引用改對(duì)象吁断?只要有引用變量引用對(duì)象趁蕊,垃圾回收機(jī)制就不會(huì)回收它。
也就是說仔役,當(dāng)java對(duì)象被創(chuàng)建出來之后掷伙,垃圾回收機(jī)制會(huì)實(shí)時(shí)的監(jiān)控每個(gè)對(duì)象的運(yùn)行狀態(tài),包括對(duì)象的申請(qǐng)又兵,引用,被引用任柜,賦值等。當(dāng)垃圾回收機(jī)制實(shí)時(shí)的監(jiān)控到某個(gè)對(duì)象不再被引用變量所引用時(shí)寒波,垃圾回收機(jī)制就會(huì)回收它所占用的空間乘盼。
基本上,可以把JVM內(nèi)存中的對(duì)象引用理解成一種有向圖俄烁,把引用變量绸栅,對(duì)象都當(dāng)成有向圖的頂點(diǎn),將引用關(guān)系當(dāng)成圖的有向邊页屠,有向邊總是從引用端指向被引用的Java對(duì)象粹胯。因?yàn)镴ava的所有對(duì)象都是由一條條線程創(chuàng)建出來的,因此可以把線程對(duì)象當(dāng)成有向圖的起始頂點(diǎn)辰企。
對(duì)于單線程程序而言风纠,整個(gè)程序只有一條main線程,那么該圖就是以main進(jìn)程為頂點(diǎn)的有向圖牢贸。在這個(gè)有向圖中竹观,main頂點(diǎn)可達(dá)的對(duì)象都處于可達(dá)狀態(tài),垃圾回收機(jī)制不會(huì)回收它們潜索;如果某個(gè)對(duì)象在這個(gè)有向圖中處于不可達(dá)狀態(tài)臭增,那么就認(rèn)為這個(gè)對(duì)象不再被引用。
采用有向圖管理內(nèi)存中的對(duì)象具有較高的精度竹习,當(dāng)缺點(diǎn)是效率較低誊抛。
當(dāng)一個(gè)對(duì)象在堆內(nèi)存中運(yùn)行時(shí),根據(jù)它在對(duì)應(yīng)有向圖中的狀態(tài)整陌,可以把它所處的狀態(tài)分成
- 可達(dá)狀態(tài):當(dāng)一個(gè)對(duì)象被創(chuàng)建后拗窃,有一個(gè)以上的引用變量引用它瞎领。在有向圖中可以從起始頂點(diǎn)導(dǎo)航到該對(duì)象,那么它就處于可達(dá)狀態(tài)随夸,程序可以通過引用變量來調(diào)用該對(duì)
象的屬性和方法九默。 - 可恢復(fù)狀態(tài):如果程序中某個(gè)對(duì)象不再有任何引用變量引用它,它將先進(jìn)入可恢復(fù)狀態(tài)逃魄,此時(shí)從有向圖的起始頂點(diǎn)不能導(dǎo)航到該對(duì)象荤西。在這種狀態(tài)下,系統(tǒng)的垃圾回
收機(jī)制準(zhǔn)備回收該對(duì)象所占用的內(nèi)存伍俘。在回收該對(duì)象之前邪锌,系統(tǒng)會(huì)調(diào)用可恢復(fù)狀態(tài)的對(duì)象的finalize方法進(jìn)行資源清理,如果系統(tǒng)調(diào)用finalize方法重新讓一個(gè)以L的
引用變量引用該對(duì)象癌瘾,則這個(gè)對(duì)象會(huì)再次變?yōu)榭蛇_(dá)狀態(tài):否則觅丰,該對(duì)象將進(jìn)入不可達(dá)狀態(tài)。 - 不可達(dá)狀態(tài):書對(duì)象的所有關(guān)聯(lián)都被切斷妨退,fl甲系統(tǒng)調(diào)用所有對(duì)象的finaii}e方法依然沒有使該對(duì)象變成可達(dá)狀態(tài)后妇萄,這個(gè)對(duì)象將永久性地失去引用,最后變成不可達(dá)狀態(tài)咬荷。只有當(dāng)一個(gè)對(duì)象處于不可達(dá)狀態(tài)時(shí)冠句,系統(tǒng)才會(huì)真正回收該對(duì)象所占有的資源。
一個(gè)對(duì)象可以被一個(gè)方法的局部變量引用幸乒,也可以被其他類的類變量引用懦底,或者被其他對(duì)象的實(shí)例變量引用。當(dāng)某個(gè)對(duì)象被其他類的類變量引用時(shí)罕扎,只有該類被銷毀后聚唐,該對(duì)象才會(huì)進(jìn)入可恢復(fù)狀態(tài);當(dāng)某個(gè)對(duì)象那個(gè)被其他對(duì)象的實(shí)例變量引用時(shí)腔召,只有當(dāng)引用該對(duì)象的對(duì)象被銷毀或變成不可達(dá)狀態(tài)后杆查,改對(duì)象才會(huì)進(jìn)入不可達(dá)狀態(tài)。
對(duì)于垃圾回收機(jī)制來說臀蛛,判斷一個(gè)對(duì)象是否可回收的標(biāo)準(zhǔn)就在改對(duì)象時(shí)候被引用亲桦,因此引用也是JVM進(jìn)行內(nèi)存管理的一個(gè)重要概念。為了更好的管理對(duì)象的引用浊仆,從JDK1.2開始烙肺,Java在java.lang.ref包下提供了三個(gè)類:SoftReference,PhantomReference和WeakReference,它們分別代表了系統(tǒng)對(duì)對(duì)象的三種引用方式:軟引用,虛引用和弱引用氧卧。歸納起來,Java語言對(duì)對(duì)象的引用有如下四種:
- 強(qiáng)引用
- 軟引用
- 弱引用
- 虛引用
2.強(qiáng)引用
當(dāng)程序創(chuàng)建一個(gè)對(duì)象氏堤,并把這個(gè)對(duì)象賦給一個(gè)引用變量沙绝,這個(gè)引用變量就是強(qiáng)引用搏明。強(qiáng)引用是最常見的。
當(dāng)一個(gè)對(duì)象被一個(gè)或一個(gè)以上的強(qiáng)引用變量所引用時(shí)闪檬,它處于可達(dá)狀態(tài)星著,它不可能被系統(tǒng)垃圾回收機(jī)制回收,即使系統(tǒng)內(nèi)存非常緊張粗悯,即使有些Java對(duì)象以后永遠(yuǎn)都不會(huì)被用到虚循,JVM也不會(huì)回收被強(qiáng)引用所引用的Java對(duì)象。
由于JVM肯定不會(huì)回收被強(qiáng)引用所引用的Java對(duì)象样傍,因此強(qiáng)引用時(shí)造成Java內(nèi)存泄漏的只要原因之一横缔。
3.軟引用
對(duì)于強(qiáng)引用所引用的Java對(duì)象而言,無論系統(tǒng)的內(nèi)存如何緊張衫哥,即使某些Java以后不再使用茎刚,垃圾回收機(jī)制也不會(huì)回收它所占的內(nèi)存。當(dāng)時(shí)軟引用不同撤逢,當(dāng)系統(tǒng)內(nèi)存充足時(shí)膛锭,和強(qiáng)引用是沒有什么區(qū)別的。但是當(dāng)系統(tǒng)內(nèi)存不足時(shí)蚊荣,軟引用所引用的Java對(duì)象可以被垃圾回收機(jī)制回收初狰,從而避免系統(tǒng)內(nèi)存的不足的異常。
當(dāng)程序需要大量創(chuàng)建某個(gè)類的新對(duì)象互例,而且有可能重新訪問已創(chuàng)建的來對(duì)象是奢入,可以充分使用軟引用來解決內(nèi)存緊張的難題。代碼如下:
class Person{
String name;
int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String toString(){
return "Person[name="+name+",age="+age+"]";
}
}
public class Main {
public static void main(String[] args) {
SoftReference<Person>[] people=new SoftReference[100000];
for(int i=0;i<people.length;i++){
people[i]=new SoftReference<Person>(new Person("HelloJack"+i,i));
}
System.out.println(people[2].get());
System.out.println(people[4].get());
System.gc();
System.runFinalization();
System.out.println(people[2].get());
System.out.println(people[4].get());
}
}
4.弱引用
軟引用與軟引用有點(diǎn)相似敲霍,區(qū)別在于弱引用所引用的對(duì)象的生命周期更短俊马。弱引用通過WeakReference類實(shí)現(xiàn)。對(duì)于軟引用的對(duì)象而言肩杈,當(dāng)系統(tǒng)垃圾回收機(jī)制運(yùn)行時(shí)柴我,不管系統(tǒng)內(nèi)存時(shí)候足夠,總會(huì)回收改對(duì)象所占用的內(nèi)存扩然。當(dāng)然艘儒,并不是說當(dāng)一個(gè)對(duì)象只有弱引用時(shí),它就會(huì)立即被回收夫偶,正如那些失去引用的對(duì)象一樣界睁,必須等到系統(tǒng)垃圾回收機(jī)制運(yùn)行時(shí)才會(huì)被回收。
弱引用具有很大的不確定性兵拢,因?yàn)槊看卫厥諜C(jī)制執(zhí)行時(shí)都會(huì)回收弱引用所引用的對(duì)象翻斟,而垃圾回收機(jī)制的運(yùn)行又不受程序員的控制,因此程序獲取弱引用所引用的Java對(duì)象是必須小心空指針異常说铃,通過弱引用所獲取的Java對(duì)象可能是null.
代碼如下:
String str=new String("HelloJack");
WeakReference<String> wr=new WeakReference<String>(str);
str=null;
System.out.println(wr.get());
System.gc();
System.runFinalization();
System.out.println(wr.get());
5.虛引用
弱引用和軟引用可以單獨(dú)使用访惜,當(dāng)虛引用不能單獨(dú)使用嘹履,單獨(dú)使用虛引用沒有太大的意義。虛引用的主要作用就是跟蹤對(duì)象被垃圾回收的狀態(tài)债热,程序可以通過檢查與虛引用關(guān)聯(lián)的引用隊(duì)列中是否包含指定的虛引用砾嫉,從而了解虛引用所引用的對(duì)象是否即將被回收。虛引用通過PhantomReference類實(shí)現(xiàn)窒篱,他完全類似于沒有引用焕刮。虛引用對(duì)對(duì)象本身沒有太大的影響,對(duì)象甚至感覺不到虛引用的存在墙杯。
代碼如下:
String str=new String("HelloJack");
ReferenceQueue<String> rq=new ReferenceQueue<String>();
PhantomReference<String> pr=new PhantomReference<>(str,rq);
str=null;
System.out.println(pr.get());
System.gc();
System.runFinalization();
System.out.println(rq.poll()==pr);
Java的內(nèi)存泄漏
程序運(yùn)行過程中會(huì)不斷地分配內(nèi)存空間配并,那些不在使用的內(nèi)存空間應(yīng)該即時(shí)被回收,從而保證系統(tǒng)可以再次使用這些內(nèi)存霍转,如果存在無用的內(nèi)存空間應(yīng)該即時(shí)被回收荐绝,從而保證系統(tǒng)可以再次使用這些內(nèi)存,如果存在無用的內(nèi)存沒有被回收回來避消,那就內(nèi)存泄漏低滩。
垃圾回收機(jī)制
垃圾回收機(jī)制只要完成兩件事:
- 跟蹤并監(jiān)控每個(gè)Java對(duì)象,當(dāng)某個(gè)對(duì)象處于不可達(dá)狀態(tài)岩喷,回收該對(duì)象所占用的內(nèi)存
- 清理內(nèi)存分配恕沫,回收過程中產(chǎn)生的內(nèi)存碎片
一個(gè)高效的JVM一個(gè)重要的方面是提供高效的垃圾回收機(jī)制,高效的垃圾回收機(jī)制可以保證垃圾回收的快速運(yùn)行纱意,避免應(yīng)用程序的性能瓶頸婶溯,又不會(huì)到時(shí)應(yīng)用程序卡頓。
1.垃圾回收的基本算法
實(shí)際上偷霉,垃圾回收機(jī)制不可能實(shí)時(shí)檢測(cè)到每個(gè)Java對(duì)象的狀態(tài)迄委,因此當(dāng)一個(gè)對(duì)象失去引用后,它也不會(huì)立即被回收类少,只有等垃圾回收機(jī)制運(yùn)行時(shí)才會(huì)被回收叙身。
對(duì)于一個(gè)垃圾回收器的設(shè)計(jì)算法來說,大致如下可供選擇的設(shè)計(jì)硫狞。
- 串行回收(Serial)和并行回收(Parallel):串行回收就是不管系統(tǒng)有多少個(gè)CPU信轿,始終只用一個(gè)CPU來執(zhí)行回收操作;而并行回收就是把整個(gè)回收工作拆分成多部分残吩,每個(gè)部分有一個(gè)CPU負(fù)責(zé)财忽,從而讓多個(gè)CPU并行回收。并行回收的執(zhí)行效率很高泣侮,但復(fù)雜度增加即彪,另外也有其他一些副作用,比如內(nèi)存碎片會(huì)增加等活尊。
- 并發(fā)執(zhí)行(Concorrent)和應(yīng)用程序(Stop-the-world)停止:Stop-the-world的垃圾回收方式在執(zhí)行垃圾回收的同時(shí)會(huì)導(dǎo)致應(yīng)用程序暫停祖凫。并發(fā)執(zhí)行的垃圾回收雖然不會(huì)導(dǎo)致應(yīng)用程序暫停琼蚯,但由于并發(fā)執(zhí)行垃圾回收需要解決和應(yīng)用程序的執(zhí)行沖突(應(yīng)用程序可能會(huì)在垃圾回收的過程中修改對(duì)象),因此并發(fā)執(zhí)行垃圾回收的系統(tǒng)開銷比Stop-the-world更高惠况,而且執(zhí)行時(shí)也需要更多的堆內(nèi)存。
- 壓縮(Compacting)/不壓縮(Non-compacting)和復(fù)制(Copying):為了減少內(nèi)存碎片宁仔,支持壓縮的垃圾回收器會(huì)把所有的活對(duì)象搬遷到一起稠屠,然后將之前占用的內(nèi)存全部回收。不壓縮的垃圾回收器只是回收內(nèi)存翎苫,這樣回收回來的內(nèi)存不可能是連續(xù)的权埠,因此將有較多的內(nèi)存碎片,相對(duì)壓縮垃圾回收機(jī)制煎谍,不壓縮垃圾回收機(jī)制回收內(nèi)存更快攘蔽,而分配內(nèi)存是就會(huì)更慢,而且無法解決內(nèi)存碎片的問題呐粘。復(fù)制
垃圾回收會(huì)將所有的可達(dá)對(duì)象復(fù)制到另一塊相同的內(nèi)存中满俗,這種方式的優(yōu)點(diǎn)是垃圾回收過程不會(huì)產(chǎn)生內(nèi)存碎片,但缺點(diǎn)也很明顯作岖,需要復(fù)制數(shù)據(jù)和額外的內(nèi)存唆垃。
上面介紹的復(fù)制,不壓縮痘儡,壓縮都是垃圾回收器回收已用內(nèi)存空間的方式辕万,關(guān)于這三種方式詳述如下:
- 復(fù)制:將堆內(nèi)存分成兩個(gè)相同空間,從根(類似有向圖起始頂點(diǎn))開始訪問每一個(gè)關(guān)聯(lián)的可達(dá)對(duì)象沉删,將空間A的可達(dá)對(duì)象全部復(fù)制到空間B渐尿,然后一次性回收整個(gè)空間A。
對(duì)于復(fù)制算法而言矾瑰,因?yàn)橹恍柙L問所有的可達(dá)對(duì)象砖茸,將所有的可達(dá)對(duì)象復(fù)制完成后就回收整個(gè)空間,完全不用理會(huì)那些不可達(dá)對(duì)象脯倚,所以遍歷空間的成本較小渔彰,但需要巨大的復(fù)制成本和較多的內(nèi)存。
標(biāo)記清除(mark-sweep):也就是不壓縮回收方式推正。垃圾回收器先從根開始訪問所有的可達(dá)對(duì)象恍涂,將他們標(biāo)記為可狀態(tài),然后再遍歷一次整個(gè)內(nèi)存區(qū)域植榕,對(duì)所有的沒有標(biāo)記為可達(dá)對(duì)象進(jìn)行垃圾回收處理再沧。
標(biāo)記壓縮(mark-sweep-compact):這是壓縮回收方式,這種方式充分利用上述兩種算法的優(yōu)點(diǎn)尊残,垃圾回收器先從根開始訪問所有的可達(dá)對(duì)象炒瘸,將它們標(biāo)記為可達(dá)狀態(tài)淤堵。接下來垃圾回收器會(huì)將這些活動(dòng)對(duì)象搬遷在一起,這個(gè)過程也被稱為內(nèi)存壓縮顷扩,然后垃圾回收機(jī)制再次回收那些不可達(dá)對(duì)象所占用的內(nèi)存空間拐邪,這樣對(duì)避免了回收產(chǎn)生內(nèi)存碎片。
上面無論用哪種回收方式隘截,具體實(shí)現(xiàn)起來總是利弊參半扎阶。因此,實(shí)際垃圾回收時(shí)總是使用多種設(shè)計(jì)方式婶芭,也就是針對(duì)不同的情況采用不同的垃圾回收方式實(shí)現(xiàn)东臀。
現(xiàn)行的垃圾回收器用分代的方式來采用不用的回收設(shè)計(jì)。分代的基本思路是根據(jù)對(duì)象生存時(shí)間的長(zhǎng)短犀农,把堆內(nèi)存分成三代:
- Young(新生代)
- Old(老年代)
- Permanent(永生代)
垃圾回收器會(huì)根據(jù)不同代的特點(diǎn)采用不同的回收算法惰赋,從而充分利用各種回收算法的優(yōu)點(diǎn)。
2.堆內(nèi)存的分代回收
分代回收的一個(gè)依據(jù)就是對(duì)象生存時(shí)間的長(zhǎng)短呵哨,然后根據(jù)不同代采取不同的垃圾回收策略赁濒。采用這種“分代回收”的策略基于如下兩點(diǎn)事實(shí)。
- 絕大多數(shù)的對(duì)象不會(huì)被長(zhǎng)時(shí)間引用仇穗,這些對(duì)象在其Young期間就會(huì)被回收流部。
- 很老的對(duì)象(生存時(shí)間很長(zhǎng))和很新的對(duì)象(生存時(shí)間很短)之間很少存在互相引用的情況。
對(duì)于Young代的對(duì)象而言纹坐,大部分對(duì)象都會(huì)很快進(jìn)入不可達(dá)狀態(tài)枝冀,只要少量的對(duì)象能熬到垃圾回收?qǐng)?zhí)行,而垃圾回收器只需保留Young代中處于可達(dá)狀態(tài)的對(duì)象耘子,如果采用復(fù)制算法只需要少量的復(fù)制成本果漾,因此大部分垃圾回收器對(duì)Young代都采用復(fù)制算法。
- Young代
對(duì)Young代采用復(fù)制算法只需遍歷那些處于可達(dá)狀態(tài)的對(duì)象谷誓,而且這些對(duì)象的數(shù)量較少绒障,可復(fù)制成本也不大,因此可以充分發(fā)揮復(fù)制算法的優(yōu)點(diǎn)捍歪。
Young代由一個(gè)Eden區(qū)和兩個(gè)Survivor區(qū)構(gòu)成户辱。絕大多數(shù)對(duì)象先分配到Eden區(qū)中(有一些大的對(duì)象可能會(huì)直接被分配到old代中),Survivor區(qū)中的對(duì)象都至少在Young代中經(jīng)歷過一次垃圾回收糙臼,所以這些對(duì)象在被轉(zhuǎn)移到old代之前會(huì)先保留在Survivor空間中庐镐。同一時(shí)間兩個(gè)Sunrtvor空間中有一個(gè)用來保存對(duì)象,而另一個(gè)是空的变逃,用來在下次垃圾回收時(shí)保存Young代中的對(duì)象必逆。每次復(fù)制就是將Aden和第一個(gè)Survivpr區(qū)的可達(dá)對(duì)象復(fù)制到第二個(gè)Survivor區(qū),然后清空Eden與第一個(gè)Survivor區(qū)。
2.Old代
如果Young代中的對(duì)象經(jīng)過數(shù)次的垃圾回收依然沒有被回收掉名眉,即這個(gè)對(duì)象經(jīng)過足夠長(zhǎng)的時(shí)間還處于可達(dá)狀態(tài)粟矿,垃圾回收機(jī)制就會(huì)將這個(gè)對(duì)象轉(zhuǎn)移到Old代。
Old代的大部分對(duì)象都是“久經(jīng)考驗(yàn)”的老人了损拢,因此它們沒有那么容易被回收陌粹。而且隨著時(shí)間的流逝,Old代的對(duì)象會(huì)越來越多福压,因此Old代的空間要比Young代的空間更大申屹。出于這兩點(diǎn)考慮,具有如下特征:
- Old垃圾回收的執(zhí)行頻率無須太高隧膏。因?yàn)楹苌儆袑?duì)象會(huì)死掉。
- 每次對(duì)Old代執(zhí)行垃圾回收都需要更長(zhǎng)的時(shí)間來完成嚷那。
基于以上考慮胞枕,垃圾回收器一般會(huì)采用標(biāo)記壓縮算法,這個(gè)算法可以避免復(fù)制Old代的大量對(duì)象魏宽,而且Old代的對(duì)象不會(huì)很快死亡腐泻,回收過程不會(huì)大量的產(chǎn)生內(nèi)存碎片。因此相對(duì)比較劃算队询。
3.Permanent代
Permanent代主要用于裝載Class,方法等信息派桩,默認(rèn)為64MB,垃圾回收機(jī)制通常不會(huì)回收Permanent代的對(duì)象蚌斩。對(duì)于那些需要加載很多類的服務(wù)器程序铆惑,往往需要加大Permanent代的內(nèi)存,否則可能因?yàn)閮?nèi)存不足而導(dǎo)致程序終止送膳。
當(dāng)Young代的內(nèi)存將要用完時(shí)员魏,垃圾回收機(jī)制會(huì)對(duì)Young代進(jìn)行垃圾回收,垃圾回收機(jī)制會(huì)采用較高的頻率對(duì)Yn}rng代進(jìn)行掃描和回收叠聋。因?yàn)檫@種回收的系統(tǒng)開銷比較小撕阎,因此也被稱為次要回收(minor collection ).當(dāng)old代的內(nèi)存將要用完時(shí),垃圾回收機(jī)制會(huì)進(jìn)行全回收碌补,也就是對(duì)Young代和old代都要進(jìn)行回收虏束,此時(shí)回收成本就大得多了,因此也稱為主要
回收(major callectivn)厦章。
通常來說镇匀,Young代的內(nèi)存會(huì)先被回收,而且會(huì)使用專門的回收算法(復(fù)制算法)來回收Young代的內(nèi)存:對(duì)于Old代的回收頻率則要低得多闷袒,因此也會(huì)采用專門的回收算法坑律。如果需要進(jìn)行內(nèi)存壓縮,那么每個(gè)代都獨(dú)立地進(jìn)行壓縮。
3.常見的垃圾回收器
1.串行回收器
串行回收器通過對(duì)Young代和Old代的回收都是串行的(只使用一個(gè)CPU)晃择,而且垃圾回收?qǐng)?zhí)行期間會(huì)使的應(yīng)用程序產(chǎn)生暫停谒养。具體策略為,Young代采用串行復(fù)制算法夹厌,Old代采用串行標(biāo)記壓縮算法稽揭。
2.并行回收器
并行回收器對(duì)于Young代采用與串行回收器基本形似的回收算法,只是增加了多CPU并行的能力浪蹂,即同時(shí)啟動(dòng)多線程并行來執(zhí)行垃圾回收抵栈。線程數(shù)默認(rèn)問CPU個(gè)數(shù),當(dāng)計(jì)算機(jī)中的CPU很多時(shí)坤次,可以用-XX:ParallelGCThreads=size來減少并行線程的數(shù)目古劲。
3.并行壓縮回收器
并行壓縮回收器的改變主要體現(xiàn)在對(duì)Old代的回收上。系統(tǒng)首先將Old代劃分成幾個(gè)固定大小的區(qū)域缰猴。在Mark階段产艾,多個(gè)垃圾回收線程會(huì)并行標(biāo)記Old代中的可達(dá)對(duì)象。當(dāng)某個(gè)對(duì)象被標(biāo)記為可達(dá)對(duì)象時(shí)滑绒,還會(huì)更新對(duì)象所在區(qū)域的大小闷堡,以及該對(duì)象的位置信息。
接下來是summary階段疑故。summary階段直接操作Old代的區(qū)域杠览,而不是單個(gè)的對(duì)象。由于每次垃圾回收的壓縮都會(huì)在Old代的左邊部分存儲(chǔ)大量的可達(dá)對(duì)象纵势,對(duì)這樣的高密度可達(dá)對(duì)象的區(qū)域進(jìn)行壓縮往往很不劃算踱阿。所以summary階段會(huì)從最左邊的區(qū)域開始檢測(cè)每個(gè)區(qū)域的密度,當(dāng)檢測(cè)到某個(gè)區(qū)域中能回收的空間達(dá)到了某個(gè)數(shù)值時(shí)(也就是可達(dá)對(duì)象的密度較小時(shí))吨悍,垃圾回收器會(huì)判定該區(qū)域扫茅,以及該區(qū)域右邊的所有區(qū)域都應(yīng)該進(jìn)行回收,而該區(qū)域左邊的區(qū)域都會(huì)被標(biāo)識(shí)為密集區(qū)域育瓜,垃圾回收器既不會(huì)把新對(duì)象移動(dòng)到這些密集區(qū)域中葫隙,也不會(huì)對(duì)這些密集區(qū)域進(jìn)行壓縮;該區(qū)域和其右邊的所有區(qū)域都會(huì)被壓縮并回收空間。summary階段目前還是串行操作躏仇,雖然并行是可以實(shí)現(xiàn)的恋脚,但重要性不如對(duì)mark和壓縮階段的并行重要。
最后是compact階段焰手≡忝瑁回收器利用summary階段生成的數(shù)據(jù)識(shí)別出有哪些區(qū)域是需要裝填的,多個(gè)垃圾回收線程可以并行地將數(shù)據(jù)復(fù)制到這些區(qū)域中书妻。經(jīng)過這個(gè)過程后船响,Old代的一端會(huì)密集地存在大量的活動(dòng)對(duì)象,另一端則存在大塊的空閑塊。
4.并發(fā)標(biāo)識(shí)-清理(Mark-Sweep)回收器(CMS)
CMS回收器對(duì)Young代的回收方式和并行回收器的回收方式完全相同见间。由于對(duì)Young代的回收依然采用復(fù)制回收算法聊闯,因此垃圾回收時(shí)依然會(huì)導(dǎo)致程序暫停,除非依靠多CPU并行來提高垃圾回收的速度米诉。
通常來說菱蔬,建議適當(dāng)加大Young代的內(nèi)存。如果Young代的內(nèi)存足夠大就不用頻繁地進(jìn)行垃圾回收了史侣,而且增大垃圾回收的時(shí)間間隔后可以讓更多的位于Young代中的Java對(duì)象自己死掉拴泌,從而避免復(fù)制。但將Young代的內(nèi)存設(shè)得過大也有一個(gè)壞處:當(dāng)垃圾回收器回收Young代的內(nèi)存時(shí)惊橱,復(fù)制成本會(huì)顯著上升(復(fù)制算法必須等Young代滿了之后才開始回收)蚪腐,所以回收時(shí)會(huì)讓系統(tǒng)的暫停時(shí)間顯著加大。
CMS對(duì)Old代的回收多數(shù)是并發(fā)操作税朴,而不是并行操作削茁。垃圾回收開始時(shí)需要一個(gè)短暫的暫停,此階段稱為初始標(biāo)識(shí)(initial mark)階段掉房,這個(gè)階段僅僅標(biāo)識(shí)出那些被直接引用的可達(dá)對(duì)象。接下來進(jìn)入并發(fā)標(biāo)識(shí)階段( concurrent marking phase)慰丛,垃圾回收器會(huì)依據(jù)在初始標(biāo)識(shí)中發(fā)現(xiàn)的可達(dá)對(duì)象來尋找其他的可達(dá)對(duì)象卓囚。由于在并發(fā)標(biāo)識(shí)階段應(yīng)用程序也會(huì)同時(shí)在運(yùn)行,無法保證所有的可達(dá)對(duì)象都被標(biāo)識(shí)出來诅病,因此應(yīng)用程序會(huì)再次很短地暫停一下哪亿,多線程并行地重新標(biāo)識(shí)之前可能因?yàn)榫l(fā)而漏掉的對(duì)象,這個(gè)階段被稱為再標(biāo)識(shí)(remark)階段贤笆。
完成了再標(biāo)識(shí)以后蝇棉,所有的可達(dá)對(duì)象都已經(jīng)被標(biāo)識(shí)出來了,接下來就可以運(yùn)行并發(fā)清理操作了芥永。
4.內(nèi)存管理小技巧
- 盡量使用直接量
當(dāng)需要使用字符串篡殷,還有Byte,Short,Integer,Long,Float,Double,Boolean,Character包裝類的實(shí)例時(shí),程序不應(yīng)該采用new的方式來創(chuàng)建對(duì)象埋涧,而應(yīng)該直接采用直接量來創(chuàng)建它們板辽。 - 使用StringBuilder和StringBuffer進(jìn)行字符串連接
String,StringBuilder,StringBuffer都可以代表字符串,其中String代表字符序列不可變的字符串棘催,而StringBuilder和StringBuffer都代表字符序列可變的字符串
如果程序使用多個(gè)String對(duì)象進(jìn)行字符串連接運(yùn)算劲弦,在運(yùn)行時(shí)將生產(chǎn)大量的臨時(shí)字符串,這些字符串會(huì)保存在內(nèi)存中從而到時(shí)程序性能下降醇坝。
- 盡早釋放無用對(duì)象的引用
大部分時(shí)候邑跪,方法的局部引用變量所引用對(duì)象會(huì)隨著方法的結(jié)束而變成垃圾,因?yàn)榫植孔兞康纳嫫谙藓芏蹋?dāng)方法運(yùn)行結(jié)束時(shí)画畅,該方法內(nèi)的局部變量就結(jié)束了生存期限砸琅。因此大部分時(shí)候程序無須將局部引用變量顯示設(shè)為null.
public void info(){
Object object=new Object();
System.out.println(object.toString());
System.out.println(object.hashCode());
object=null;
}
上面的方法隨著info()方法執(zhí)行完成,obj引用變量的作用域就結(jié)束了夜赵,原來的obj所引用的對(duì)象就會(huì)變成垃圾明棍。因此object=null是沒有必要的。但是如下代碼:
public void info(){
Object object=new Object();
System.out.println(object.toString());
System.out.println(object.hashCode());
object=null;
//執(zhí)行耗時(shí)寇僧,耗內(nèi)存操作
//或者調(diào)用耗時(shí)摊腋,耗內(nèi)存的方法
}
上面因?yàn)樾枰獔?zhí)行耗內(nèi)存耗時(shí)的方法,可以盡早的釋放對(duì)Object對(duì)象的引用嘁傀。所以可能的是程序在執(zhí)行耗時(shí)兴蒸,耗內(nèi)存操作時(shí),obj之前所引用的Object對(duì)象可能被垃圾回收了细办。
- 盡量少用靜態(tài)變量
從理論上來說橙凳,Java對(duì)象何時(shí)被回收由垃圾回收機(jī)制決定,對(duì)程序員來說是不確定的笑撞。由于垃圾回收機(jī)制判斷一個(gè)對(duì)象是否是垃圾的唯一標(biāo)準(zhǔn)是該對(duì)象是否有引用變量引用它岛啸,因此推薦盡早釋放對(duì)象的引用。
最好的情況是茴肥,某個(gè)對(duì)象被static變量所引用坚踩,那么垃圾回收機(jī)制通常是不會(huì)回收這個(gè)對(duì)象所占的內(nèi)存的。如下代碼:
class Person{
static Obejct obj=new Object();
}
Obj變量是Person類的靜態(tài)變量瓤狐,因此它的生命周期與Person類同步瞬铸。在Person類不被卸載的情況下,Person類對(duì)應(yīng)的Class對(duì)象會(huì)常駐內(nèi)存础锐,直到程序運(yùn)行結(jié)束嗓节。因此,obj所引用的Object對(duì)象一旦被創(chuàng)建皆警,也會(huì)常駐內(nèi)存拦宣,知道程序結(jié)束。
- 避免在經(jīng)常調(diào)用的方法信姓,循環(huán)中創(chuàng)建Java對(duì)象
經(jīng)常調(diào)用的方法和循環(huán)有一個(gè)共同特征:這些代碼段會(huì)被多次重復(fù)調(diào)用恢着。如下:
for(int i=0;i<10;i++){
Object obj=new Object();
}
雖然上面的obj是局部變量,執(zhí)行完之后會(huì)失效财破,當(dāng)時(shí)要循環(huán)創(chuàng)建10次掰派,系統(tǒng)要不斷地分配空間,執(zhí)行初始化操作左痢。這些對(duì)象的生存時(shí)間又不長(zhǎng)靡羡,所以系統(tǒng)又要回收它們所占的內(nèi)存空間系洛,在這些操作中會(huì)消耗不少性能。
- 緩存經(jīng)常使用的對(duì)象
如果有些對(duì)象需要被經(jīng)常使用略步,則可以考慮把這些對(duì)象用緩存池保存起來描扯,,這樣當(dāng)下次需要時(shí)就可直接拿出這些對(duì)象來用趟薄,典型的緩存就是數(shù)據(jù)連接池绽诚,數(shù)據(jù)連接池緩存了大量的數(shù)據(jù)庫連接,每次需要訪問數(shù)據(jù)庫是都可以直接去除數(shù)據(jù)庫連接杭煎。
除此之外恩够,系統(tǒng)的一些常用基礎(chǔ)信息也可以通過緩存的方式存起來。實(shí)現(xiàn)緩存一般有兩種方式:
1.使用HashMap進(jìn)行緩存(不宜存儲(chǔ)過多數(shù)據(jù)羡铲,從而內(nèi)存過大蜂桶,導(dǎo)致性能下降)
2.直接使用某些開源項(xiàng)目進(jìn)行緩存
- 盡量不要使用finalize()方法
當(dāng)一個(gè)對(duì)象失去引用之后,垃圾回收器準(zhǔn)備回收該對(duì)象之前也切,垃圾回收機(jī)制會(huì)先調(diào)用改對(duì)象的finalize()方法來執(zhí)行資源清理扑媚。處于這種考慮,可能有些開發(fā)者會(huì)考慮使用finalize()方法來進(jìn)行資源清理雷恃。
實(shí)際上疆股,將資源清理放在finalize()方法中完成是非常拙劣的選擇。根據(jù)前面介紹的垃圾回收算法倒槐,垃圾回收機(jī)制工作量已經(jīng)夠大了押桃,尤其是回收Young代內(nèi)存時(shí), 大都會(huì)引起應(yīng)用程序暫停导犹,使得用戶難以忍受。
在垃圾回收器本身已經(jīng)嚴(yán)重制約應(yīng)用程序性能的情況下羡忘,如果再選擇使用finalize()方法進(jìn)行資源清理谎痢,無疑是一種火上澆油的行為,這將導(dǎo)致垃圾回收器的負(fù)擔(dān)更大卷雕,導(dǎo)致程序運(yùn)行效率更差节猿。
- 考慮使用SoftReference
當(dāng)程序需要?jiǎng)?chuàng)建長(zhǎng)度很大的數(shù)組時(shí),可以考慮使用SoftReference來包裝數(shù)組元素漫雕,而不是直接讓數(shù)組元素來引用對(duì)象滨嘱。
SoftReference是一個(gè)很好的選擇,當(dāng)內(nèi)存充足時(shí)不回收數(shù)據(jù)浸间,當(dāng)內(nèi)存不充足時(shí)釋放軟引用所引用的對(duì)象太雨。