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