GC 基础
= GC 基礎(chǔ) =====================
JAVA堆的描述如下:
內(nèi)存由 Perm 和 Heap 組成. 其中
Heap = {Old + NEW = { Eden , from, to } }
JVM內(nèi)存模型中分兩大塊,一塊是 NEW Generation, 另一塊是Old Generation. 在New Generation中,有一個(gè)叫Eden的空間,主要是用來(lái)存放新生的對(duì)象,還有兩個(gè)Survivor Spaces(from,to), 它們用來(lái)存放每次垃圾回收后存活下來(lái)的對(duì)象。在Old Generation中,主要存放應(yīng)用程序中生命周期長(zhǎng)的內(nèi)存對(duì)象,還有個(gè)Permanent Generation,主要用來(lái)放JVM自己的反射對(duì)象,比如類(lèi)對(duì)象和方法對(duì)象等。
垃圾回收描述:
在New Generation塊中,垃圾回收一般用Copying的算法,速度快。每次GC的時(shí)候,存活下來(lái)的對(duì)象首先由Eden拷貝到某個(gè)Survivor Space, 當(dāng)Survivor Space空間滿了后, 剩下的live對(duì)象就被直接拷貝到Old Generation中去。因此,每次GC后,Eden內(nèi)存塊會(huì)被清空。在Old Generation塊中,垃圾回收一般用mark-compact的算法,速度慢些,但減少內(nèi)存要求.
垃圾回收分多級(jí),0級(jí)為全部(Full)的垃圾回收,會(huì)回收OLD段中的垃圾;1級(jí)或以上為部分垃圾回收,只會(huì)回收NEW中的垃圾,內(nèi)存溢出通常發(fā)生于OLD段或Perm段垃圾回收后,仍然無(wú)內(nèi)存空間容納新的Java對(duì)象的情況。
當(dāng)一個(gè)URL被訪問(wèn)時(shí),內(nèi)存申請(qǐng)過(guò)程如下:
A. JVM會(huì)試圖為相關(guān)Java對(duì)象在Eden中初始化一塊內(nèi)存區(qū)域
B. 當(dāng)Eden空間足夠時(shí),內(nèi)存申請(qǐng)結(jié)束。否則到下一步
C. JVM試圖釋放在Eden中所有不活躍的對(duì)象(這屬于1或更高級(jí)的垃圾回收), 釋放后若Eden空間仍然不足以放入新對(duì)象,則試圖將部分Eden中活躍對(duì)象放入Survivor區(qū)
D. Survivor區(qū)被用來(lái)作為Eden及OLD的中間交換區(qū)域,當(dāng)OLD區(qū)空間足夠時(shí),Survivor區(qū)的對(duì)象會(huì)被移到Old區(qū),否則會(huì)被保留在Survivor區(qū)
E. 當(dāng)OLD區(qū)空間不夠時(shí),JVM會(huì)在OLD區(qū)進(jìn)行完全的垃圾收集(0級(jí))
F. 完全垃圾收集后,若Survivor及OLD區(qū)仍然無(wú)法存放從Eden復(fù)制過(guò)來(lái)的部分對(duì)象,導(dǎo)致JVM無(wú)法在Eden區(qū)為新對(duì)象創(chuàng)建內(nèi)存區(qū)域,則出現(xiàn)”out of memory錯(cuò)誤”
JVM調(diào)優(yōu)建議:
ms/mx:定義YOUNG+OLD段的總尺寸,ms為JVM啟動(dòng)時(shí)YOUNG+OLD的內(nèi)存大小;mx為最大可占用的YOUNG+OLD內(nèi)存大小。在用戶生產(chǎn)環(huán)境上一般將這兩個(gè)值設(shè)為相同,以減少運(yùn)行期間系統(tǒng)在內(nèi)存申請(qǐng)上所花的開(kāi)銷(xiāo)。
NewSize/MaxNewSize:定義YOUNG段的尺寸,NewSize為JVM啟動(dòng)時(shí)YOUNG的內(nèi)存大小;MaxNewSize為最大可占用的YOUNG內(nèi)存大小。在用戶生產(chǎn)環(huán)境上一般將這兩個(gè)值設(shè)為相同,以減少運(yùn)行期間系統(tǒng)在內(nèi)存申請(qǐng)上所花的開(kāi)銷(xiāo)。
PermSize/MaxPermSize:定義Perm段的尺寸,PermSize為JVM啟動(dòng)時(shí)Perm的內(nèi)存大小;MaxPermSize為最大可占用的Perm內(nèi)存大小。在用戶生產(chǎn)環(huán)境上一般將這兩個(gè)值設(shè)為相同,以減少運(yùn)行期間系統(tǒng)在內(nèi)存申請(qǐng)上所花的開(kāi)銷(xiāo)。
SurvivorRatio:設(shè)置Survivor空間和Eden空間的比例
內(nèi)存溢出的可能性
1. OLD段溢出
這種內(nèi)存溢出是最常見(jiàn)的情況之一,產(chǎn)生的原因可能是:
1) 設(shè)置的內(nèi)存參數(shù)過(guò)小(ms/mx, NewSize/MaxNewSize)
2) 程序問(wèn)題
單個(gè)程序持續(xù)進(jìn)行消耗內(nèi)存的處理,如循環(huán)幾千次的字符串處理,對(duì)字符串處理應(yīng)建議使用StringBuffer。此時(shí)不會(huì)報(bào)內(nèi)存溢出錯(cuò),卻會(huì)使系統(tǒng)持續(xù)垃 圾收集,無(wú)法處理其它請(qǐng)求,相關(guān)問(wèn)題程序可通過(guò)Thread Dump獲取(見(jiàn)系統(tǒng)問(wèn)題診斷一章)單個(gè)程序所申請(qǐng)內(nèi)存過(guò)大,有的程序會(huì)申請(qǐng)幾十乃至幾百兆內(nèi)存,此時(shí)JVM也會(huì)因無(wú)法申請(qǐng)到資源而出現(xiàn)內(nèi)存溢出,對(duì)此首 先要找到相關(guān)功能,然后交予程序員修改,要找到相關(guān)程序,必須在Apache日志中尋找。
當(dāng)Java對(duì)象使用完畢后,其所引用的對(duì)象卻沒(méi)有銷(xiāo)毀,使得JVM認(rèn)為他還是活躍的對(duì)象而不進(jìn)行回收,這樣累計(jì)占用了大量?jī)?nèi)存而無(wú)法釋放。由于目前市面上還沒(méi)有對(duì)系統(tǒng)影響小的內(nèi)存分析工具,故此時(shí)只能和程序員一起定位。
2. Perm段溢出
通常由于Perm段裝載了大量的Servlet類(lèi)而導(dǎo)致溢出,目前的解決辦法:
1) 將PermSize擴(kuò)大,一般256M能夠滿足要求
2) 若別無(wú)選擇,則只能將servlet的路徑加到CLASSPATH中,但一般不建議這么處理
3. C Heap溢出
系統(tǒng)對(duì)C Heap沒(méi)有限制,故C Heap發(fā)生問(wèn)題時(shí),Java進(jìn)程所占內(nèi)存會(huì)持續(xù)增長(zhǎng),直到占用所有可用系統(tǒng)內(nèi)存
其他:
JVM有2個(gè)GC線程。第一個(gè)線程負(fù)責(zé)回收Heap的Young區(qū)。第二個(gè)線程在Heap不足時(shí),遍歷Heap,將Young 區(qū)升級(jí)為Older區(qū)。Older區(qū)的大小等于-Xmx減去-Xmn,不能將-Xms的值設(shè)的過(guò)大,因?yàn)榈诙€(gè)線程被迫運(yùn)行會(huì)降低JVM的性能。
為什么一些程序頻繁發(fā)生GC?有如下原因:
l ? ? ? ? 程序內(nèi)調(diào)用了System.gc()或Runtime.gc()。
l ? ? ? ? 一些中間件軟件調(diào)用自己的GC方法,此時(shí)需要設(shè)置參數(shù)禁止這些GC。
l ? ? ? ? Java的Heap太小,一般默認(rèn)的Heap值都很小。
l ? ? ? ? 頻繁實(shí)例化對(duì)象,Release對(duì)象。此時(shí)盡量保存并重用對(duì)象,例如使用StringBuffer()和String()。
如果你發(fā)現(xiàn)每次GC后,Heap的剩余空間會(huì)是總空間的50%,這表示你的Heap處于健康狀態(tài)。許多Server端的Java程序每次GC后最好能有65%的剩余空間。
經(jīng)驗(yàn)之談:
1.Server端JVM最好將-Xms和-Xmx設(shè)為相同值。為了優(yōu)化GC,最好讓-Xmn值約等于-Xmx的1/3[2]。
2.一個(gè)GUI程序最好是每10到20秒間運(yùn)行一次GC,每次在半秒之內(nèi)完成[2]。
注意:
1.增加Heap的大小雖然會(huì)降低GC的頻率,但也增加了每次GC的時(shí)間。并且GC運(yùn)行時(shí),所有的用戶線程將暫停,也就是GC期間,Java應(yīng)用程序不做任何工作。
2.Heap大小并不決定進(jìn)程的內(nèi)存使用量。進(jìn)程的內(nèi)存使用量要大于-Xmx定義的值,因?yàn)镴ava為其他任務(wù)分配內(nèi)存,例如每個(gè)線程的Stack等。
2.Stack的設(shè)定
每個(gè)線程都有他自己的Stack。
| -Xss | 每個(gè)線程的Stack大小 |
?
Stack的大小限制著線程的數(shù)量。如果Stack過(guò)大就好導(dǎo)致內(nèi)存溢漏。-Xss參數(shù)決定Stack大小,例如-Xss1024K。如果Stack太小,也會(huì)導(dǎo)致Stack溢漏。3.硬件環(huán)境
硬件環(huán)境也影響GC的效率,例如機(jī)器的種類(lèi),內(nèi)存,swap空間,和CPU的數(shù)量。
如果你的程序需要頻繁創(chuàng)建很多transient對(duì)象,會(huì)導(dǎo)致JVM頻繁GC。這種情況你可以增加機(jī)器的內(nèi)存,來(lái)減少Swap空間的使用[2]。
4.4種GC
第一種為單線程GC,也是默認(rèn)的GC。,該GC適用于單CPU機(jī)器。
第二種為T(mén)hroughput GC,是多線程的GC,適用于多CPU,使用大量線程的程序。第二種GC與第一種GC相似,不同在于GC在收集Young區(qū)是多線程的,但在Old區(qū)和第一種一樣,仍然采用單線程。-XX:+UseParallelGC參數(shù)啟動(dòng)該GC。
第三種為Concurrent Low Pause GC,類(lèi)似于第一種,適用于多CPU,并要求縮短因GC造成程序停滯的時(shí)間。這種GC可以在Old區(qū)的回收同時(shí),運(yùn)行應(yīng)用程序。-XX:+UseConcMarkSweepGC參數(shù)啟動(dòng)該GC。
第四種為Incremental Low Pause GC,適用于要求縮短因GC造成程序停滯的時(shí)間。這種GC可以在Young區(qū)回收的同時(shí),回收一部分Old區(qū)對(duì)象。-Xincgc參數(shù)啟動(dòng)該GC。
按照基本回收策略分
引用計(jì)數(shù)(Reference Counting):
比較古老的回收算法。原理是此對(duì)象有一個(gè)引用,即增加一個(gè)計(jì)數(shù),刪除一個(gè)引用則減少一個(gè)計(jì)數(shù)。垃圾回收時(shí),只用收集計(jì)數(shù)為0的對(duì)象。此算法最致命的是無(wú)法處理循環(huán)引用的問(wèn)題。
?
標(biāo)記-清除(Mark-Sweep):
?
?
?
此算法執(zhí)行分兩階段。第一階段從引用根節(jié)點(diǎn)開(kāi)始標(biāo)記所有被引用的對(duì)象,第二階段遍歷整個(gè)堆,把未標(biāo)記的對(duì)象清除。此算法需要暫停整個(gè)應(yīng)用,同時(shí),會(huì)產(chǎn)生內(nèi)存碎片。
?
復(fù)制(Copying):
?
?
?
此算法把內(nèi)存空間劃為兩個(gè)相等的區(qū)域,每次只使用其中一個(gè)區(qū)域。垃圾回收時(shí),遍歷當(dāng)前使用區(qū)域,把正在使用中的對(duì)象復(fù)制到另外一個(gè)區(qū)域中。算法每次只處理 正在使用中的對(duì)象,因此復(fù)制成本比較小,同時(shí)復(fù)制過(guò)去以后還能進(jìn)行相應(yīng)的內(nèi)存整理,不會(huì)出現(xiàn)“碎片”問(wèn)題。當(dāng)然,此算法的缺點(diǎn)也是很明顯的,就是需要兩倍 內(nèi)存空間。
?
標(biāo)記-整理(Mark-Compact):
?
?
?
此算法結(jié)合了“標(biāo)記-清除”和“復(fù)制”兩個(gè)算法的優(yōu)點(diǎn)。也是分兩階段,第一階段從根節(jié)點(diǎn)開(kāi)始標(biāo)記所有被引用對(duì)象,第二階段遍歷整個(gè)堆,把清除未標(biāo)記對(duì)象并 且把存活對(duì)象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標(biāo)記-清除”的碎片問(wèn)題,同時(shí)也避免了“復(fù)制”算法的空間問(wèn)題。
?
按分區(qū)對(duì)待的方式分
增量收集(Incremental Collecting):實(shí)時(shí)垃圾回收算法,即:在應(yīng)用進(jìn)行的同時(shí)進(jìn)行垃圾回收。不知道什么原因JDK5.0中的收集器沒(méi)有使用這種算法的。
?
分代收集(Generational Collecting):基于對(duì)對(duì)象生命周期分析后得出的垃圾回收算法。把對(duì)象分為年青代、年老代、持久代,對(duì)不同生命周期的對(duì)象使用不同的算法(上述方式中的一個(gè))進(jìn)行回收?,F(xiàn)在的垃圾回收器(從J2SE1.2開(kāi)始)都是使用此算法的。
?
按系統(tǒng)線程分
串行收集:串行收集使用單線程處理所有垃圾回收工作,因?yàn)闊o(wú)需多線程交互,實(shí)現(xiàn)容易,而且效率比較高。但是,其局限性也比較明顯,即無(wú)法使用多處理器的優(yōu)勢(shì),所以此收集適合單處理器機(jī)器。當(dāng)然,此收集器也可以用在小數(shù)據(jù)量(100M左右)情況下的多處理器機(jī)器上。
?
并行收集:并行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數(shù)目越多,越能體現(xiàn)出并行收集器的優(yōu)勢(shì)。(串型收集的并發(fā)版本,需要暫停jvm) 并行paralise指的是多個(gè)任務(wù)在多個(gè)cpu中一起并行執(zhí)行,最后將結(jié)果合并。效率是N倍。
?
并發(fā)收集:相對(duì)于串行收集和并行收集而言,前面 兩個(gè)在進(jìn)行垃圾回收工作時(shí),需要暫停整個(gè)運(yùn)行環(huán)境,而只有垃圾回收程序在運(yùn)行,因此,系統(tǒng)在垃圾回收時(shí)會(huì)有明顯的暫停,而且暫停時(shí)間會(huì)因?yàn)槎言酱蠖介L(zhǎng)。 (和并行收集不同,并發(fā)只有在開(kāi)頭和結(jié)尾會(huì)暫停jvm)并發(fā)concurrent指的是多個(gè)任務(wù)在一個(gè)cpu偽同步執(zhí)行,但其實(shí)是串行調(diào)度的,效率并非直 接是N倍。
?
分代垃圾回收
??? 分代的垃圾回收策略,是基于這樣一個(gè)事實(shí):不同的對(duì)象的生命周期是不一樣的。因此,不同生命周期的對(duì)象可以采取不同的收集方式,以便提高回收效率。
?
?? ?在Java程序運(yùn)行的過(guò)程中,會(huì)產(chǎn)生大量的對(duì)象,其中有些對(duì)象是與業(yè)務(wù)信息相關(guān),比如Http請(qǐng)求中的Session對(duì)象、線程、Socket連接,這 類(lèi)對(duì)象跟業(yè)務(wù)直接掛鉤,因此生命周期比較長(zhǎng)。但是還有一些對(duì)象,主要是程序運(yùn)行過(guò)程中生成的臨時(shí)變量,這些對(duì)象生命周期會(huì)比較短,比如:String對(duì) 象,由于其不變類(lèi)的特性,系統(tǒng)會(huì)產(chǎn)生大量的這些對(duì)象,有些對(duì)象甚至只用一次即可回收。
?
?? ?試想,在不進(jìn)行對(duì)象存活時(shí)間區(qū)分的情況下,每次垃圾回收都是對(duì)整個(gè)堆空間進(jìn)行回收,花費(fèi)時(shí)間相對(duì)會(huì)長(zhǎng),同時(shí),因?yàn)槊看位厥斩夹枰闅v所有存活對(duì)象,但實(shí) 際上,對(duì)于生命周期長(zhǎng)的對(duì)象而言,這種遍歷是沒(méi)有效果的,因?yàn)榭赡苓M(jìn)行了很多次遍歷,但是他們依舊存在。因此,分代垃圾回收采用分治的思想,進(jìn)行代的劃 分,把不同生命周期的對(duì)象放在不同代上,不同代上采用最適合它的垃圾回收方式進(jìn)行回收。
?
?
如圖所示:
?
?? ?虛擬機(jī)中的共劃分為三個(gè)代:年輕代(Young Generation)、年老點(diǎn)(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類(lèi)的類(lèi)信息,與垃圾收集要收集的Java對(duì)象關(guān)系不大。年輕代和年老代的劃分是對(duì)垃圾收集影響比較大的。
?
?
年輕代:
?? ?所有新生成的對(duì)象首先都是放在年輕代的。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對(duì)象。年輕代分三個(gè)區(qū)。一個(gè)Eden區(qū),兩個(gè) Survivor區(qū)(一般而言)。大部分對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí),還存活的對(duì)象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè)),當(dāng)這個(gè) Survivor區(qū)滿時(shí),此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū),當(dāng)這個(gè)Survivor區(qū)也滿了的時(shí)候,從第一個(gè)Survivor區(qū)復(fù)制 過(guò)來(lái)的并且此時(shí)還存活的對(duì)象,將被復(fù)制“年老區(qū)(Tenured)”。需要注意,Survivor的兩個(gè)區(qū)是對(duì)稱(chēng)的,沒(méi)先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí) 存在從Eden復(fù)制過(guò)來(lái) 對(duì)象,和從前一個(gè)Survivor復(fù)制過(guò)來(lái)的對(duì)象,而復(fù)制到年老區(qū)的只有從第一個(gè)Survivor去過(guò)來(lái)的對(duì)象。而且,Survivor區(qū)總有一個(gè)是空 的。同時(shí),根據(jù)程序需要,Survivor區(qū)是可以配置為多個(gè)的(多于兩個(gè)),這樣可以增加對(duì)象在年輕代中的存在時(shí)間,減少被放到年老代的可能。
?
年老代:
?? ?在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象,就會(huì)被放到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象。
?
持久代:
?? ?用于存放靜態(tài)文件,如今Java類(lèi)、方法等。持久代對(duì)垃圾回收沒(méi)有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate 等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來(lái)存放這些運(yùn)行過(guò)程中新增的類(lèi)。持久代大小通過(guò)-XX:MaxPermSize=<N>進(jìn)行設(shè) 置。
?
什么情況下觸發(fā)垃圾回收?
由于對(duì)象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時(shí)間也不一樣。GC有兩種類(lèi)型:Scavenge GC和Full GC。
?
Scavenge GC
?? ?一般情況下,當(dāng)新對(duì)象生成,并且在Eden申請(qǐng)空間失敗時(shí),就會(huì)觸發(fā)Scavenge GC,對(duì)Eden區(qū)域進(jìn)行GC,清除非存活對(duì)象,并且把尚且存活的對(duì)象移動(dòng)到Survivor區(qū)。然后整理Survivor的兩個(gè)區(qū)。這種方式的GC是對(duì) 年輕代的Eden區(qū)進(jìn)行,不會(huì)影響到年老代。因?yàn)榇蟛糠謱?duì)象都是從Eden區(qū)開(kāi)始的,同時(shí)Eden區(qū)不會(huì)分配的很大,所以Eden區(qū)的GC會(huì)頻繁進(jìn)行。因 而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來(lái)。
?
Full GC
?? ?對(duì)整個(gè)堆進(jìn)行整理,包括Young、Tenured和Perm。Full GC因?yàn)樾枰獙?duì)整個(gè)對(duì)進(jìn)行回收,所以比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù)。在對(duì)JVM調(diào)優(yōu)的過(guò)程中,很大一部分工作就是對(duì)于FullGC的調(diào)節(jié)。有如下原因可能導(dǎo)致Full GC:
· 年老代(Tenured)被寫(xiě)滿
· 持久代(Perm)被寫(xiě)滿?
· System.gc()被顯示調(diào)用?
·上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化
?
?
?
?
?
?
?
?
?
?
?
?
= G1 ===================================
傳說(shuō)中的G1,傳說(shuō)中的low-pause垃圾收集。Java SE 6的update14版本中已經(jīng)包含測(cè)試版,可以在啟動(dòng)時(shí)加JVM參數(shù)來(lái)啟用
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC
http://www.blogjava.net/BlueDavy/archive/2009/03/11/259230.html
本文摘自《構(gòu)建高性能的大型分布式Java應(yīng)用》一 書(shū),Garbage First簡(jiǎn)稱(chēng)G1,它的目標(biāo)是要做到盡量減少GC所導(dǎo)致的應(yīng)用暫停的時(shí)間,讓?xiě)?yīng)用達(dá)到準(zhǔn)實(shí)時(shí)的效果,同時(shí)保持JVM堆空間的利用率,將作為CMS的替代 者在JDK 7中閃亮登場(chǎng),其最大的特色在于允許指定在某個(gè)時(shí)間段內(nèi)GC所導(dǎo)致的應(yīng)用暫停的時(shí)間最大為多少,例如在100秒內(nèi)最多允許GC導(dǎo)致的應(yīng)用暫停時(shí)間為1秒, 這個(gè)特性對(duì)于準(zhǔn)實(shí)時(shí)響應(yīng)的系統(tǒng)而言非常的吸引人,這樣就再也不用擔(dān)心系統(tǒng)突然會(huì)暫停個(gè)兩三秒了。
G1要做到這樣的效果,也是有前提的,一方面是硬件環(huán)境的要求,必須是多核的CPU以及較大的內(nèi)存(從規(guī)范來(lái)看,512M以上就滿足條件了),另外一方面是需要接受吞吐量的稍微降低,對(duì)于實(shí)時(shí)性要求高的系統(tǒng)而言,這點(diǎn)應(yīng)該是可以接受的。
為了能夠達(dá)到這樣的效果,G1在原有的各種GC策略上進(jìn)行了吸收和改進(jìn),在G1中可以看到增量收集器和CMS的影子,但它不僅僅是吸收原有GC策略的優(yōu) 點(diǎn),并在此基礎(chǔ)上做出了很多的改進(jìn),簡(jiǎn)單來(lái)說(shuō),G1吸收了增量GC以及CMS的精髓,將整個(gè)jvm Heap劃分為多個(gè)固定大小的region,掃描時(shí)采用Snapshot-at-the-beginning的并發(fā)marking算法(具體在后面內(nèi)容詳 細(xì)解釋)對(duì)整個(gè)heap中的region進(jìn)行mark,回收時(shí)根據(jù)region中活躍對(duì)象的bytes進(jìn)行排序,首先回收活躍對(duì)象bytes小以及回收耗 時(shí)短(預(yù)估出來(lái)的時(shí)間)的region,回收的方法為將此region中的活躍對(duì)象復(fù)制到另外的region中,根據(jù)指定的GC所能占用的時(shí)間來(lái)估算能回 收多少region,這點(diǎn)和以前版本的Full GC時(shí)得處理整個(gè)heap非常不同,這樣就做到了能夠盡量短時(shí)間的暫停應(yīng)用,又能回收內(nèi)存,由于這種策略在回收時(shí)首先回收的是垃圾對(duì)象所占空間最多的 region,因此稱(chēng)為Garbage First。
看完上面對(duì)于G1策略的簡(jiǎn)短描述,并不能清楚的掌握G1,在繼續(xù)詳細(xì)看G1的步驟之前,必須先明白G1對(duì)于JVM Heap的改造,這些對(duì)于習(xí)慣了劃分為new generation、old generation的大家來(lái)說(shuō)都有不少的新意。
G1將Heap劃分為多個(gè)固定大小的region,這也是G1能夠?qū)崿F(xiàn)控制GC導(dǎo)致的應(yīng)用暫停時(shí)間的前提,region之間的對(duì)象引用通過(guò) remembered set來(lái)維護(hù),每個(gè)region都有一個(gè)remembered set,remembered set中包含了引用當(dāng)前region中對(duì)象的region的對(duì)象的pointer,由于同時(shí)應(yīng)用也會(huì)造成這些region中對(duì)象的引用關(guān)系不斷的發(fā)生改 變,G1采用了Card Table來(lái)用于應(yīng)用通知region修改remembered sets,Card Table由多個(gè)512字節(jié)的Card構(gòu)成,這些Card在Card Table中以1個(gè)字節(jié)來(lái)標(biāo)識(shí),每個(gè)應(yīng)用的線程都有一個(gè)關(guān)聯(lián)的remembered set log,用于緩存和順序化線程運(yùn)行時(shí)造成的對(duì)于card的修改,另外,還有一個(gè)全局的filled RS buffers,當(dāng)應(yīng)用線程執(zhí)行時(shí)修改了card后,如果造成的改變僅為同一region中的對(duì)象之間的關(guān)聯(lián),則不記錄remembered set log,如造成的改變?yōu)榭鐁egion中的對(duì)象的關(guān)聯(lián),則記錄到線程的remembered set log,如線程的remembered set log滿了,則放入全局的filled RS buffers中,線程自身則重新創(chuàng)建一個(gè)新的remembered set log,remembered set本身也是一個(gè)由一堆cards構(gòu)成的哈希表。
盡管G1將Heap劃分為了多個(gè)region,但其默認(rèn)采用的仍然是分代的方式,只是僅簡(jiǎn)單的劃分為了年輕代(young)和非年輕代,這也是由于G1仍 然堅(jiān)信大多數(shù)新創(chuàng)建的對(duì)象都是不需要長(zhǎng)的生命周期的,對(duì)于應(yīng)用新創(chuàng)建的對(duì)象,G1將其放入標(biāo)識(shí)為young的region中,對(duì)于這些region,并不 記錄remembered set logs,掃描時(shí)只需掃描活躍的對(duì)象,G1在分代的方式上還可更細(xì)的劃分為:fully young或partially young,fully young方式暫停的時(shí)候僅處理young regions,partially同樣處理所有的young regions,但它還會(huì)根據(jù)允許的GC的暫停時(shí)間來(lái)決定是否要加入其他的非young regions,G1是運(yùn)行到fully-young方式還是partially young方式,外部是不能決定的,在啟動(dòng)時(shí),G1采用的為fully-young方式,當(dāng)G1完成一次Concurrent Marking后,則切換為partially young方式,隨后G1跟蹤每次回收的效率,如果回收f(shuō)ully-young中的regions已經(jīng)可以滿足內(nèi)存需要的話,那么就切換回fully young方式,但當(dāng)heap size的大小接近滿的情況下,G1會(huì)切換到partially young方式,以保證能提供足夠的內(nèi)存空間給應(yīng)用使用。
除了分代方式的劃分外,G1還支持另外一種pure G1的方式,也就是不進(jìn)行代的劃分,pure方式和分代方式的具體不同在下面的具體執(zhí)行步驟中進(jìn)行描述。
掌握了這些概念后,繼續(xù)來(lái)看G1的具體執(zhí)行步驟:
1.?????????Initial Marking
G1對(duì)于每個(gè)region都保存了兩個(gè)標(biāo)識(shí)用的bitmap,一個(gè)為previous marking bitmap,一個(gè)為next marking bitmap,bitmap中包含了一個(gè)bit的地址信息來(lái)指向?qū)ο蟮钠鹗键c(diǎn)。
開(kāi)始Initial Marking之前,首先并發(fā)的清空next marking bitmap,然后停止所有應(yīng)用線程,并掃描標(biāo)識(shí)出每個(gè)region中root可直接訪問(wèn)到的對(duì)象,將region中top的值放入next top at mark start(TAMS)中,之后恢復(fù)所有應(yīng)用線程。
觸發(fā)這個(gè)步驟執(zhí)行的條件為:
l??G1定義了一個(gè)JVM Heap大小的百分比的閥值,稱(chēng)為h,另外還有一個(gè)H,H的值為(1-h)*Heap Size,目前這個(gè)h的值是固定的,后續(xù)G1也許會(huì)將其改為動(dòng)態(tài)的,根據(jù)jvm的運(yùn)行情況來(lái)動(dòng)態(tài)的調(diào)整,在分代方式下,G1還定義了一個(gè)u以及soft limit,soft limit的值為H-u*Heap Size,當(dāng)Heap中使用的內(nèi)存超過(guò)了soft limit值時(shí),就會(huì)在一次clean up執(zhí)行完畢后在應(yīng)用允許的GC暫停時(shí)間范圍內(nèi)盡快的執(zhí)行此步驟;
l??在pure方式下,G1將marking與clean up組成一個(gè)環(huán),以便clean up能充分的使用marking的信息,當(dāng)clean up開(kāi)始回收時(shí),首先回收能夠帶來(lái)最多內(nèi)存空間的regions,當(dāng)經(jīng)過(guò)多次的clean up,回收到?jīng)]多少空間的regions時(shí),G1重新初始化一個(gè)新的marking與clean up構(gòu)成的環(huán)。
2.?????????Concurrent Marking
按照之前Initial Marking掃描到的對(duì)象進(jìn)行遍歷,以識(shí)別這些對(duì)象的下層對(duì)象的活躍狀態(tài),對(duì)于在此期間應(yīng)用線程并發(fā)修改的對(duì)象的以來(lái)關(guān)系則記錄到remembered set logs中,新創(chuàng)建的對(duì)象則放入比top值更高的地址區(qū)間中,這些新創(chuàng)建的對(duì)象默認(rèn)狀態(tài)即為活躍的,同時(shí)修改top值。
3.?????????Final Marking Pause
當(dāng)應(yīng)用線程的remembered set logs未滿時(shí),是不會(huì)放入filled RS buffers中的,在這樣的情況下,這些remebered set logs中記錄的card的修改就會(huì)被更新了,因此需要這一步,這一步要做的就是把應(yīng)用線程中存在的remembered set logs的內(nèi)容進(jìn)行處理,并相應(yīng)的修改remembered sets,這一步需要暫停應(yīng)用,并行的運(yùn)行。
4.?????????Live Data Counting and Cleanup
值得注意的是,在G1中,并不是說(shuō)Final Marking Pause執(zhí)行完了,就肯定執(zhí)行Cleanup這步的,由于這步需要暫停應(yīng)用,G1為了能夠達(dá)到準(zhǔn)實(shí)時(shí)的要求,需要根據(jù)用戶指定的最大的GC造成的暫停時(shí) 間來(lái)合理的規(guī)劃什么時(shí)候執(zhí)行Cleanup,另外還有幾種情況也是會(huì)觸發(fā)這個(gè)步驟的執(zhí)行的:
l??G1采用的是復(fù)制方法來(lái)進(jìn)行收集,必須保證每次的”to space”的空間都是夠的,因此G1采取的策略是當(dāng)已經(jīng)使用的內(nèi)存空間達(dá)到了H時(shí),就執(zhí)行Cleanup這個(gè)步驟;
l??對(duì)于full-young和partially-young的分代模式的G1而言,則還有情況會(huì)觸發(fā)Cleanup的執(zhí)行,full-young模 式下,G1根據(jù)應(yīng)用可接受的暫停時(shí)間、回收young regions需要消耗的時(shí)間來(lái)估算出一個(gè)yound regions的數(shù)量值,當(dāng)JVM中分配對(duì)象的young regions的數(shù)量達(dá)到此值時(shí),Cleanup就會(huì)執(zhí)行;partially-young模式下,則會(huì)盡量頻繁的在應(yīng)用可接受的暫停時(shí)間范圍內(nèi)執(zhí)行 Cleanup,并最大限度的去執(zhí)行non-young regions的Cleanup。
這一步中GC線程并行的掃描所有region,計(jì)算每個(gè)region中低于next TAMS值中marked data的大小,然后根據(jù)應(yīng)用所期望的GC的短延時(shí)以及G1對(duì)于region回收所需的耗時(shí)的預(yù)估,排序region,將其中活躍的對(duì)象復(fù)制到其他 region中。
?
G1為了能夠盡量的做到準(zhǔn)實(shí)時(shí)的響應(yīng),例如估算暫停時(shí)間的算法、對(duì)于經(jīng)常被引用的對(duì)象的特殊 處理等,G1為了能夠讓GC既能夠充分的回收內(nèi)存,又能夠盡量少的導(dǎo)致應(yīng)用的暫停,可謂費(fèi)盡心思,從G1的論文中的性能評(píng)測(cè)來(lái)看效果也是不錯(cuò)的,不過(guò)如果 G1能允許開(kāi)發(fā)人員在編寫(xiě)代碼時(shí)指定哪些對(duì)象是不用mark的就更完美了,這對(duì)于有巨大緩存的應(yīng)用而言,會(huì)有很大的幫助,G1將隨JDK 6 Update 14?beta發(fā)布。
轉(zhuǎn)載于:https://www.cnblogs.com/wuxiang/p/3947753.html
總結(jié)
- 上一篇: [转] 数学符号英文拼写及发音
- 下一篇: SQL 模糊查询技术