內(nèi)存管理機(jī)制中講述了java運(yùn)行時(shí)區(qū)域的各個(gè)部分,其中程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧3個(gè)區(qū)域隨線(xiàn)程而生,隨線(xiàn)程而滅。而java堆和方法區(qū)則不一樣,這個(gè)部分的內(nèi)存的分配和回收都是動(dòng)態(tài)的,垃圾收集器所關(guān)注的是這部分的內(nèi)存。在堆中,垃圾收集器的回收率比較高,尤其是新生代,一次大約可以回收70%到95%的空間。而方法區(qū)(永久代)的回收效率遠(yuǎn)低于此。
一、怎么判斷對(duì)象是無(wú)用的對(duì)象?垃圾收集器主要對(duì)被判定無(wú)用的對(duì)象進(jìn)行回收。有以下幾種算法:1、引用計(jì)數(shù)算法;2、可達(dá)性分析算法。
1、引用計(jì)數(shù)算法是當(dāng)一個(gè)地方引用它是計(jì)數(shù)器就加1,引用失效時(shí)計(jì)數(shù)器就減1,計(jì)數(shù)器為0時(shí)就是沒(méi)有被引用的對(duì)象。很多主流虛擬機(jī)沒(méi)有使用這個(gè)算法,因?yàn)樗茈y解決對(duì)象之間互相循環(huán)引用的問(wèn)題;
2、可達(dá)性分析算法的基本思路是通過(guò)一系列被稱(chēng)為“GC Roots”的對(duì)象最為起點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索走過(guò)的路徑稱(chēng)為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有引用鏈時(shí),就判定對(duì)象無(wú)引用??墒菫镚C Roots的對(duì)象有:虛擬機(jī)棧(棧幀中的本地變量表)中的引用對(duì)象、方法區(qū)中的類(lèi)靜態(tài)屬性引用的對(duì)象、方法區(qū)中常量池引用的對(duì)象、本地方法棧中JNI(一般說(shuō)的本地方法,即Native方法)引用的對(duì)象。
二、什么是引用?如果reference類(lèi)型的數(shù)據(jù)中儲(chǔ)存的數(shù)據(jù)代表的另一塊內(nèi)存的地址,那這個(gè)數(shù)據(jù)就是一個(gè)引用。JDK1.2之后對(duì)引用進(jìn)行了擴(kuò)展,分為:? 1、強(qiáng)引用;2、軟引用3、弱引用;4、虛引用。
1、強(qiáng)引用就是引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象;
2、軟引用是在系統(tǒng)將要發(fā)生內(nèi)存溢出前把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行二次回收,如果內(nèi)存還是不足,才拋出內(nèi)存溢出異常,軟引用使用SoftReference類(lèi)來(lái)實(shí)現(xiàn);
3、弱引用是對(duì)象只能活到下一次垃圾收集發(fā)生之前,無(wú)論內(nèi)存是否足夠,都會(huì)被回收掉。弱引用使用WeakReference類(lèi)實(shí)現(xiàn);
4、虛引用完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用獲得對(duì)象實(shí)例。虛引用的唯一作用就是在實(shí)例被回收之前收到系統(tǒng)的通知。使用PhantomReference類(lèi)來(lái)實(shí)現(xiàn)虛引用。
三、對(duì)象的finalize()方法。對(duì)象在被判定沒(méi)有引用后和被垃圾回收之前會(huì)至少進(jìn)行2次標(biāo)記,第一次為可達(dá)性算法判定對(duì)象沒(méi)有引用鏈時(shí),會(huì)對(duì)還有必要執(zhí)行finalize()方法的對(duì)象進(jìn)行標(biāo)記,當(dāng)對(duì)象沒(méi)有覆蓋finalize()方法或者該方法已經(jīng)被執(zhí)行過(guò)后,虛擬機(jī)將視為沒(méi)有必要執(zhí)行此方法。之后隊(duì)列被放進(jìn)一個(gè)F-Queue隊(duì)列等待執(zhí)行finalize()方法,然后GC將對(duì)對(duì)象進(jìn)行第二次標(biāo)記。
四、方法區(qū)(永久代)的回收內(nèi)容主要是兩部分:廢棄常量和無(wú)用的類(lèi)。廢棄常量:如常量池中的“abc”,如果沒(méi)有一個(gè)String對(duì)象叫做“abc”也沒(méi)有其他類(lèi)使用“abc”,那這個(gè)常量就是廢棄常量。判定一個(gè)類(lèi)是無(wú)用的類(lèi)有三個(gè)必要條件:1、該類(lèi)所有的實(shí)例都已經(jīng)被回收,也就是java堆中不存在該類(lèi)的實(shí)例;2、加載該類(lèi)的ClassLoader已經(jīng)被回收;3、該類(lèi)對(duì)java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法再任何地方通過(guò)反射訪(fǎng)問(wèn)該類(lèi)的方法。
五、垃圾收集算法
1、標(biāo)記-清除算法? 是將被標(biāo)記為可回收的對(duì)象進(jìn)行清除,此算法有兩大問(wèn)題:效率問(wèn)題和空間問(wèn)題。因?yàn)闃?biāo)記和清除兩個(gè)過(guò)程效率都不高,并且清除后的內(nèi)存空間不連續(xù);
2、復(fù)制算法? 是將內(nèi)存分為兩塊,一塊滿(mǎn)時(shí)就將存活的對(duì)象按順序的復(fù)制到另一塊內(nèi)存中,然后將原有的類(lèi)刪除。有點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效,但是可用內(nèi)存減小了一半。為此HotSpot虛擬機(jī)默認(rèn)Eden區(qū)和Survivor區(qū)的比例是8:1,以Eden區(qū)和一塊Suvivor區(qū)作為新生代,另一塊Suvivor區(qū)作為保留區(qū)域,每當(dāng)垃圾回收時(shí)將存活的對(duì)象復(fù)制到保留區(qū)Suvivor中,清除新生代的所有對(duì)象。如果存在對(duì)象100%存活的場(chǎng)景,不能使用此算法
3、標(biāo)記-整理算法? 標(biāo)記的過(guò)程和標(biāo)記-清除算法相同,然后讓所有的存活對(duì)象向一邊移動(dòng),然后將存活端邊的對(duì)象清除。
4、分代收集算法? 只是把堆分成新生代和老年代,然后根據(jù)不同的代使用不同的回收算法。新生代一般使用復(fù)制算法,老年代則必須使用標(biāo)記-清除算法或者標(biāo)記-整理算法
六、收集器是收集算法的具體實(shí)現(xiàn)
1、Serial收集器(新生代、單線(xiàn)程、復(fù)制算法)? 是最基本、發(fā)展歷史最悠久的收集器。它在JDK1.3之前是新生代的唯一選擇。這個(gè)收集器是一個(gè)單線(xiàn)程收集器,這里的單線(xiàn)程是指在收集器工作時(shí),必須暫停其他線(xiàn)程的工作,一般稱(chēng)為Stop?The World。新生代采用復(fù)制算法,暫停所有線(xiàn)程。
2、ParNew收集器(新生代、多線(xiàn)程、復(fù)制算法)? 其實(shí)就是Serial收集器的多線(xiàn)程版本,出了使用多條線(xiàn)程進(jìn)行垃圾收集之外,其余行為包括Serial收集器可用所有控制參數(shù)(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The world、對(duì)象分配規(guī)則、回收策略等都與Serial收集器一樣。使用-XX:+UseConcMarkSweepGC選項(xiàng)后默認(rèn)使用ParNew收集器,也可以使用-XX:UseParNewGC來(lái)選擇使用??梢允褂?XX:ParallelGCTreads來(lái)限制垃圾收集的線(xiàn)程數(shù)。
3、parallel Scavenge收集器(新生代、多線(xiàn)程、復(fù)制算法)? 它也是采用復(fù)制算法的收集器,看上去和ParNew都一樣,但是Parallel Scavenge收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量。所謂吞吐量,是CPU用于運(yùn)行用戶(hù)代碼的時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶(hù)代碼時(shí)間/(運(yùn)行用戶(hù)代碼時(shí)間+垃圾收集時(shí)間)。高吞吐量可盡快的運(yùn)行完用戶(hù)的代碼。Parallel Scavenge收集器提供了兩個(gè)準(zhǔn)確控制吞吐量的參數(shù),分別是控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMills參數(shù)和直接設(shè)置垃圾收集時(shí)間和總時(shí)間的比例的-XX:GCTimeRatio。MaxGCPauseMills參數(shù)調(diào)的越大,垃圾收集次數(shù)越頻繁,吞吐量就越小。打開(kāi)-XX:+UseAdaptiveSizePolicy參數(shù)后,就不需要手工配置新生代大小,Eden區(qū)和Suvivor區(qū)的比例等參數(shù)了。
4、Serail Old收集器(老年代,單線(xiàn)程、標(biāo)記-整理算法)??
5、Parallel Old收集器(老年代、多線(xiàn)程、標(biāo)記-整理算法)?
6、CMS收集器(老年代、多線(xiàn)程并發(fā)、標(biāo)記清除算法)? 整個(gè)過(guò)程分為4個(gè)步驟:1、初始標(biāo)記(Stop The World);2、并發(fā)標(biāo)記;3、重新標(biāo)記(Stop The World);4、并發(fā)清除。但是有三個(gè)缺點(diǎn):1、對(duì)CPU資源敏感;2、無(wú)法處理浮動(dòng)垃圾(在垃圾回收過(guò)程中產(chǎn)生的類(lèi)),JDK1.5之后老年代在使用了68%內(nèi)存后默認(rèn)開(kāi)啟CMS收集器,可以通過(guò)-XX:CMSInitiatingOccupancyFranction參數(shù)來(lái)設(shè)置觸發(fā)半分比;3、標(biāo)記清除算法帶來(lái)的空間使用不充分,可以開(kāi)啟-XX:+UseCMSCompactAtFullColection參數(shù)在觸發(fā)Full GC前整合碎片空間,但是停頓時(shí)間會(huì)相應(yīng)變長(zhǎng)。另一個(gè)參數(shù)-XX:CMSFullGCCsBeforeCompaction是設(shè)置多少次Full GC后必須出現(xiàn)一次整合。
7、G1收集器? 結(jié)合了以上收集器的特點(diǎn),并行并發(fā),分代收集(G1收集器能獨(dú)立管理整個(gè)GC堆),空間整合(G1收集器從整體上看是基于標(biāo)記-整理算法實(shí)現(xiàn)的,從局部上看是基于復(fù)制算法實(shí)現(xiàn)的),可預(yù)測(cè)的停頓。
六、內(nèi)存分配
1、對(duì)象優(yōu)先在Eden分配,當(dāng)Eden區(qū)內(nèi)存不足時(shí)會(huì)發(fā)生一次Minor GC,可通過(guò)-XX:+PrintGCDetails打印日志。
2、大對(duì)象直接進(jìn)入老年代,需要連續(xù)內(nèi)存空間的Java對(duì)象比如很長(zhǎng)的字符串和數(shù)組,程序應(yīng)該避免這種寫(xiě)法??梢酝ㄟ^(guò)設(shè)置-XX:PertenureSizeThreshold參數(shù)來(lái)判斷內(nèi)存大于多少的對(duì)象直接被放到老年代。
3、長(zhǎng)期存活的對(duì)象將進(jìn)入老年代,在Eden區(qū)中經(jīng)過(guò)一側(cè)Minor GC后并且成功進(jìn)入到Survivor區(qū)的對(duì)象年齡計(jì)數(shù)器加1,并且在之后的Minor
GC發(fā)生后繼續(xù)累加,累加到閾值后進(jìn)入老年代,可以通過(guò)-XX:MaxTenuringThreshold參數(shù)設(shè)置這個(gè)閾值。
4、動(dòng)態(tài)對(duì)象年齡判定是指虛擬機(jī)并不是永遠(yuǎn)的要求對(duì)象年齡必須達(dá)到閾值后才能被轉(zhuǎn)移到老年代,如果Survivor區(qū)中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于等于該年齡的對(duì)象直接被轉(zhuǎn)移到老年代。
5、空間分配擔(dān)保是指在發(fā)生Minor GC之前虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那么Minor GC可以確保是安全的。如果不成立,則虛擬機(jī)會(huì)查看HandelPromotionFailure設(shè)置值是否允許擔(dān)保失敗,如果允許。那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次轉(zhuǎn)移到老年代對(duì)象的平均大小,如果大于,則嘗試一次Minor GC,這次GC會(huì)有風(fēng)險(xiǎn)。如果小于就進(jìn)行一次FULL GC(清理整個(gè)堆)。