❶ GC垃圾回收(3)- 三色標記演算法
CMS過程在上篇文章 GC垃圾回收(2) 中已經寫過。
它分為四個階段:
其中 並發標記 階段會有漏標的問題,為解決這個問題,採用了 "三色標記凳旦演算法"
G1 GC(Garbage First Garbage Collector)是一種服務端應用使用的垃圾收集器,目標是用在 多核、大內存 的機器上,它在大多數情況下可以實現指定的GC暫停時間,同時還能保持較高的吞吐量。它的吞吐量相較PS+PO降低了大概10%~15%,但是大大降低了響應時間,大概200ms的程度
G1內存模型如下:
G1相較之前其它的垃圾回收器,對模型進行了改變,不再進行物理分代,採用邏輯分代。
它不再將連續內存分為Eden區和Old區,而是將內存分為一個個的Region。一塊Region(分區)在邏輯上依然分代,分為兆粗指四種:Eden,Old,Survivor,Humongous(大對象,跨多個連續的Region)。
它的每個分區都可能是年輕代也可能是老年代,但是在同一時刻只能屬於某個代。
年輕代、倖存區、老年代這些概念還存在,成為了邏輯上的概念,這樣方便復用之前分代框架的邏輯。在物理上不需要連續,這帶來了額外的好處——有的分區內垃圾對象特別多,有的分區內垃圾對象很少,G1會優先回收垃圾對象特別多的分區,這樣可以花費較少的時間來回收這些分區的垃圾,這也就是G1名字的由來,即首先回收垃圾最多的分區。
新生代其實並不適用於這種演算法,依然是在新生代滿了的時候,對整個新生代進行回收——整個新生代中的對象,要麼被回收、要麼晉升,至於新生代也採取分區機制的原因,則是因為這樣跟老年代的策略統一,方便調整代的大小。
G1還是一種帶壓縮的收集器,在回收老年代的分區時,是將存活的對象從一個分區拷貝到另一個可用分區,這個拷貝的過程就實現了局部的壓縮。每個分區的大小從1M到32M不等,但都是2的冪次方。
特點:
G1與CMS在並發收集時的演算法沒太大區別,用的是 三色標記 演算法。但ZGC和Shenandoah使用的是 顏色指針 Colored Pointers。
主要用於分代模型中幫助垃圾回收。
為什麼需要 card table ?
尋找存活對象並不是一件容易的事。從一個GC root對象尋找,可能被Old區對象引用,這個Old區對象又被Eden區對象引用,那麼判斷Eden區對象是否存活就需要遍歷整個Old區存活對象看是否被Old區對象引用。這樣的話每進行一次YGC就要掃描整個Old區。
所以JVM內部,將內存區域分為一個個的card,對象存在一個個的card里。當老年代某個card中的對象指向了年輕代,就會將這個card標記為 Dirty 。這么多card具體哪個是 Dirty的,用點陣圖BitMap來代表(如0110010010,1表示Dirty),這就是Card Table。
Card Table :由於做YGC時,需要掃描整個Old區,效率非常低,所以JVM設計了Card Table, 如果一個Old區Card Table中有對象指向Y區,就將它設為Dirty,下次掃描時,只需要掃描Dirty Card。 在結構上,Card Table用BitMap來實現。
RSet會佔用一定的空間,所以ZGC又做了改進,不使用RSet,用顏色指針來標記。
Rset與賦值的效率:
5% ~ 60%(新生代)
G1能跟蹤STW停頓時間,根族配據停頓時間動態調整新生代(Y區)比例
超過單個region的 50% 就是一個大對象,也可跨越多個region。
注意: G1也是存在FGC的,並且一定會被觸發。當對象分配不下是會產生FGC。
回收時不分新生代還是老年代什麼的,region滿了就回收。
MixedGC過程:
跟CMS非常像,MixedGC最後是篩選回收,多了個篩選步驟。篩選就是找出垃圾最多的region。篩選後將存活對象復制到其他region,再將之前的region清空。
CMS和G1在並發標記時使用的是同一個演算法: 三色標記法 ,使用白灰黑三種顏色標記對象。白色是未標記;灰色自身被標記,引用的對象未標記;黑色自身與引用對象都已標記。
在remark過程中,黑色指向了白色,如果不對黑色重新掃描,則會漏標。會把白色D對象當作沒有新引用指向從而回收掉。
並發標記過程中,Mutator刪除了所有從灰色到白色的引用,會產生漏標。此時白色對象應該被回收
產生漏標問題的條件有兩個:
1.黑色對象指向了白色對象
2.灰色對象指向白色對象的引用消失
所以要解決漏標問題,打破兩個條件之一即可:
為什麼G1採用SATB而不用incremental update?
因為採用incremental update把黑色重新標記為灰色後,之前掃描過的還要再掃描一遍,效率太低。
G1有RSet與SATB相配合。Card Table里記錄了RSet,RSet里記錄了其他對象指向自己的引用,這樣就不需要再掃描其他區域,只要掃描RSet就可以了。
也就是說 灰色-->白色 引用消失時,如果沒有 黑色-->白色,引用會被push到堆棧,下次掃描時拿到這個引用,由於有RSet的存在,不需要掃描整個堆去查找指向白色的引用,效率比較高。SATB配合RSet渾然天成。
❷ JVM的垃圾演算法有哪幾種
一、垃圾收集器概述
如上圖所示,垃圾回收演算法一共有7個,3個屬於年輕代、三個屬於年老代,G1屬於橫跨年輕代和年老代的演算法。
JVM會從年輕代和年老代各選出一個演算法進行組合,連線表示哪些演算法可以組合使用
二、各個垃圾收集器說明
1、Serial(年輕代)
年輕代收集器,可以和Serial Old、CMS組合使用
採用復制演算法
使用單線程進行垃圾回收,回收時會導致Stop The World,用戶進程停止
client模式年輕代默認演算法
GC日誌關鍵字:DefNew(Default New Generation)
圖示(Serial+Serial Old)
7、G1
G1收集器由於沒有使用過,所以從網上找了一些教程供大家了解
並行與並發
分代收集
空間整合
可預測的停頓
❸ java有哪些垃圾回收演算法
常用的垃圾回收演算法有:
(1).引用計數演算法:
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器都為0的對象就是不再被使用的,垃圾收集器將回收該對象使用的內存。
引用計數演算法實現簡單,效率很高,微軟的COM技術、ActionScript、Python等都使用了引用計數演算法進行內存管理,但是引用計數演算法對於對象之間相互循環引用問題難以解決,因此java並沒有使用引用計數演算法。
(2).根搜索演算法:
通過一系列的名為「GC Root」的對象作為起點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時,則該對象不可達,該對象是不可使用的,垃圾收集器將回收其所佔的內存。
主流的商用程序語言C#、java和Lisp都使用根搜素演算法進行內存管理。
在java語言中,可作為GC Root的對象包括以下幾種對象:
a. java虛擬機棧(棧幀中的本地變數表)中的引用的對象。
b.方法區中的類靜態屬性引用的對象。
c.方法區中的常量引用的對象。
d.本地方法棧中JNI本地方法的引用對象。
java方法區在Sun HotSpot虛擬機中被稱為永久代,很多人認為該部分的內存是不用回收的,java虛擬機規范也沒有對該部分內存的垃圾收集做規定,但是方法區中的廢棄常量和無用的類還是需要回收以保證永久代不會發生內存溢出。
判斷廢棄常量的方法:如果常量池中的某個常量沒有被任何引用所引用,則該常量是廢棄常量。
判斷無用的類:
(1).該類的所有實例都已經被回收,即java堆中不存在該類的實例對象。
(2).載入該類的類載入器已經被回收。
(3).該類所對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射機制訪問該類的方法。
Java中常用的垃圾收集演算法:
(1).標記-清除演算法:
最基礎的垃圾收集演算法,演算法分為「標記」和「清除」兩個階段:首先標記出所有需要回收的對象,在標記完成之後統一回收掉所有被標記的對象。
標記-清除演算法的缺點有兩個:首先,效率問題,標記和清除效率都不高。其次,標記清除之後會產生大量的不連續的內存碎片,空間碎片太多會導致當程序需要為較大對象分配內存時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
(2).復制演算法:
將可用內存按容量分成大小相等的兩塊,每次只使用其中一塊,當這塊內存使用完了,就將還存活的對象復制到另一塊內存上去,然後把使用過的內存空間一次清理掉。這樣使得每次都是對其中一塊內存進行回收,內存分配時不用考慮內存碎片等復雜情況,只需要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
復制演算法的缺點顯而易見,可使用的內存降為原來一半。
(3).標記-整理演算法:
標記-整理演算法在標記-清除演算法基礎上做了改進,標記階段是相同的標記出所有需要回收的對象,在標記完成之後不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,在移動過程中清理掉可回收的對象,這個過程叫做整理。
標記-整理演算法相比標記-清除演算法的優點是內存被整理以後不會產生大量不連續內存碎片問題。
復制演算法在對象存活率高的情況下就要執行較多的復制操作,效率將會變低,而在對象存活率高的情況下使用標記-整理演算法效率會大大提高。
(4).分代收集演算法:
根據內存中對象的存活周期不同,將內存劃分為幾塊,java的虛擬機中一般把內存劃分為新生代和年老代,當新創建對象時一般在新生代中分配內存空間,當新生代垃圾收集器回收幾次之後仍然存活的對象會被移動到年老代內存中,當大對象在新生代中無法找到足夠的連續內存時也直接在年老代中創建。
❹ GC是什麼意思
常用的GC演算法: 1)標記非活動對象 --何為非活動對象,通俗的講,就是無引用的對象。追蹤root對象演算法: 深度追蹤root對象,將heap中所有被引用到的root做標志,所有未被標志的對象視為非活動對象,所佔用的空間視為非活動內存。2)清理非活動對象 Copy演算法: 方法:將內存分為兩個區域(from space和to space)。所有的對象分配內存都分配到from space。在清理非活動對象階段,把所有標志為活動的對象,到to space,之後清楚from space空間。然後互換from sapce和to space的身份。既原先的from space變成to sapce,原先的to space變成from space。每次清理,重復上述過程。優點:演算法不理會非活動對象,數量僅僅取決為活動對象的數量。並且在的同時,整理了heap空間,即,to space的空間使用始終是連續的,內存使用效率得到提高。缺點:劃分from space和to space,內存的使用率是1/2。Compaction演算法: 方法:在清理非活動對象階段,刪除非活動對象佔用內存,並且把活動對象向heap的底部移動,直到所有的活動對象被移到heap的一側。優點:無須劃分from sapce和to space,提高內存的使用率。並且compaction後的內存空間也是連續分配的。缺點:該演算法相對比較復雜。sun jdk gc介紹: 在減少gc之前,先來看看來自IBM的一組統計數據: 98%的java對象,在創建之後不久就變成了非活動對象;只有2%的對象,會在長時間一直處於活動狀態。如果能對這兩種對象區分對象,那麼會提交GC的效率。在sun jdk gc中(具體的說,是在jdk1.4之後的版本),提出了不同生命周期的GC策略。young generation: 生命周期很短的對象,歸為young generation。由於生命周期很短,這部分對象在gc的時候,很大部分的對象已經成為非活動對象。因此針對young generation的對象,採用演算法,只需要將少量的存活下來的對象到to space。存活的對象數量越少,那麼演算法的效率越高。young generation的gc稱為minor gc。經過數次minor gc,依舊存活的對象,將被移出young generation,移到tenured generation(下面將會介紹) young generation分為: eden:每當對象創建的時候,總是被分配在這個區域 survivor1:演算法中的from space survivor2:演算法中的to sapce (備註:其中survivor1和survivor2的身份在每次minor gc後被互換) minor gc的時候,會把eden survivor1(2)的對象到survivor2(1)去。tenured generation: 生命周期較常的對象,歸入到tenured generation。一般是經過多次minor gc,還 依舊存活的對象,將移入到tenured generation。(當然,在minor gc中如果存活的對象的超過survivor的容量,放不下的對象會直接移入到tenured generation) tenured generation的gc稱為major gc,就是通常說的full gc。採用compactiion演算法。由於tenured generaion區域比較大,而且通常對象生命周期都比較常,compaction需要一定時間。所以這部分的gc時間比較長。minor gc可能引發full gc。當eden+from space的空間大於tenured generation區的剩餘空間時,會引發full gc。這是悲觀演算法,要確保eden+from space的對象如果都存活,必須有足夠的tenured generation空間存放這些對象。Permanet Generation: 該區域比較穩定,主要用於存放classloader信息,比如類信息和method信息。對於spring hibernate這些需要動態類型支持的框架,這個區域需要足夠的空間。這部分內容相對比較理論,可以結合jstat,jmap等命令(當然也可以使用jconsole,jprofile,gciewer等工具),觀察jdk gc的情
❺ jdk8中,GC用到的的演算法有哪些
主要應用Mark-sweepalgorithm(標記消除演算法)即從根object(程序直接訪問的)開始標記可到達的object算明缺漏法基於有向圖,採用深度優先搜索最後推薦你扮型一本書,《java程序設激爛計語言》([美]KenArnold,JamesGosling,DavidHolmes)上面有對
❻ 幾種常見GC簡介
在springboot-admin當中,大概會有以下幾種類型的gc出現,本文我們看看他們分別是什麼意思。
本文使用的垃圾收集器是jdk1.8的PS+PO。
顧名思義,就是內存分配失敗導致的GC,常見於年輕代當中。
使用JNI臨界區的方式操作數組或者字元串時,為了防止GC過程中jarray或者jstring發生位移,而導致數組指針失效,需要保持它們在JVM Heap中的地址在JNI Critical過程中保持不變。於是JVM實現了GC_locker,用於JNI Critical內阻止其他GC的發生。
當GCLocker被激活且需要發生GC的時候(這里是否需要GC是各種GC發生時,調用GCLocker::check_active_before_gc()函數check並設置_needs_gc = true的),就會阻塞其他線程進入JNI臨界區;並且在最後一個位於JNI臨界區的線程退出臨界區時,發起一次CGCause為_gc_locker的GC。這里解釋了GCLocker Initiated GC發生的原委。
在JVM中的垃圾收集器中的Ergonomics就是負責自動的調解gc暫停時間和吞吐量之間的平衡,使你的虛擬機性能更好的一種做法。
簡單說就是內存在進行分配的時候,會通過一些演算法,預估是否會出現無法分配的問題。如果符合無法分配預估值,會提前進行一次gc。
這個gc主要發生的條件是元空間,也就是Metadata的參數設置問題。
通常根據我們學習的JVM只是,元空間使用的是本地內存,所以應該與當前伺服器的最大內存有關。
但實際不是這樣的,在jdk1.8中,如果不設置元空間的大小,會有一個默認值是 21M 。
所以需要我們啟動的時候指定一個元空間大小:
GC日誌如下所示:
❼ GC的復制演算法和標記整理演算法
復制演算法
復制演算法是將內存劃分為兩個區間,在任意時間點,所有動態分配的對象都只能分配在其中的一個區間(稱為活動區間),而另外一個區間(空閑區間)是空閑的。
當有效內存空間耗盡時,JVM將暫停程序運行,開啟復制GC線程。接下來GC線程會將活動區的存活對象,全部復制到空閑區間,且嚴格按照內存地址依次排列,與此同時,GC線程將更新存活對象的內存引用地址指向新的內存地址。
此時,空閑區間已經與活動區間交換,而垃圾對象現在已經全部留在了原來的活動區間,也就是現在的空閑區間。事實上,在活動區間轉換為空閑區間的同時,垃圾對象已經被一次性全部回收了。
開始時:
在被GC線程處理後:
可以看到,1和4號對象被清楚了,而2,3,5,6號對象則是規則的排列在剛才的空閑區間,也就是現在的活動區間之間,此時左半部分已經變成空閑區間,然而,如中在下一次GC之後,左半部分再次變成活動區間。
顯然:
復制演算法缺點時非常明顯的:
1.直接浪費了一般的內存。
2.再就是加入對象存活率非常高,達到了極端的100%,那麼我們需要將所有的對象都復制一遍,並將所有引用地址重置一遍。復制這一工作所花費的時間,在對象存活率達到一定的程度時,將會變得不可忽視。
由此可見,復制演算法要想使用,最起碼對象的存活率要非常低才行,而且最重要的是,我們態悔必須要克服50%內存浪費。
標記整理演算法
分為兩個階段:
1.標記:遍歷GC roots,然後將存活的對象標記。
2.整理:移動所帆橡正有存活的對象,且按照內存地址次序依次排列,然後將末端內存地址以後的內存全部回收,這個階段才為整理階段。
它與GC 前後與復制演算法類似,只不過沒有了活動區間和空閑區間
倘若此時GC線程開始工作,那麼緊接著開始的就是標記階段了。此階段與標記/清除演算法的標記階段是一樣一樣的,我們看標記階段過後對象的狀態,如下圖。
接下來,便應該是整理階段了。我們來看當整理階段處理完以後,內存的布局是如何的,如下圖。
❽ JVM YoungGen(新生代),OldGen(年老代),和PermGen(持久區)
Sun的JVM將整個堆分為三代:YoungGen(新生代),OldGen(年老代),和PermGen(持久區):
Minor GC:通常是指對新生代的回收。
Major GC:通常是指對年老代的回收。
Full GC:Major GC除並發gc外均需對整個堆進行掃描和回收。
復制拷貝演算法:要拷貝大量數據,不會產生碎片。
標記演算法:從引用根節點開始標記所有被引用的對象,把未被引用的對象清除。要遍雀笑歷所有對象,會產生碎片。
young 又分為eden,survivor1(from space ),survivor2(to sapce ).youngGen區裡面的對象的生命周期比較短,gc對這些對象進行回收的時候採用復制拷貝演算法。
eden 每當一個對象創建的時候分配的這個區域。當eden無法分配時,觸發一次Minor gc。gc每次回收的時候都將eden區存活的對象和survivor1中的對象拷貝到survivor2中,eden和survivor1清空;當gc執行下次回收的時候將eden和survivor2中的對象拷貝到surivor1中,清空eden和survivor2。依次這樣頃猜含執行;經過數次回收將依然存活的對象復制到OldGen區。
OldGen 當對象從年輕代晉升到老年代之前,會檢測老年區的剩餘空間是否大於要晉升對象的大小,如果小於則直接進行一次Full GC,以便讓老年去騰出更多的空間,然後再進行Minor GC,把年輕代的對象復制到老年代;如果大於,則根據條件(HandlePromotionFailure設置)進行Minor GC 和 Full GC。
老年區採用標記演算法,因為老年區對象的生命周期都是比較長的,採用拷貝演算法要兆帶拷貝大量的數據。採用標記演算法每次gc回收都要遍歷所有的對象。
PermGen 主要存放載入進來的類信息,包括方法,屬性,常量池等,滿了之後可能會引起out of memory 錯誤。
❾ 深入理解GC垃圾回收機制
在我們程序運行中會不斷創建新的對象,這些對象會存儲在內存中,如果沒有一套機制來回收這些內存,那麼被佔用的內存會越來越多,可用內存會越來越少,直至內存被消耗完。於是就有了一套垃圾回收機制來做這件維持系統平衡的任務。
1.確保被引用對象的內存不被錯誤的回收
2.回收不再被引用的對象的內存空間
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時, 計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。
優點:引用計數收集器可以很快地執行,交織在程序的運行之中。
缺點:很難處理循環引用,比如上圖中相互引用的兩個對象,計數器不為0,則無法釋放,但是這樣的對象存在是沒有意義的,空占內存了。
引用計數法處理不了的相互引用的問題,那麼就有了可達性分析來解決了這個問題。
從GC Roots作為起點,向下搜索它們引用的對象,可以生成一棵引用樹,樹的節點視為可達對象,反之最終不能與GC Roots有引用關系的視為不可達,不可達對象即為垃圾回收對象。
我自己的理解是,皇室家族每過一段時間,會進行皇室成員排查,從皇室第一代開始往下找血緣關系的後代,如果你跟第一代皇室沒有關系,那麼你就會被剔除皇室家族。
1.虛擬機棧中引用的對象(正在運行的方法使用到的變數、參數等)
2.方法區中類靜態屬性引用的對象(static關鍵字聲明的欄位)
3.方法區中常量引用的對象,(也就是final關鍵字聲明的欄位)
4.本地方法棧中引用的對象(native方法)
1.顯示地賦予某個對象為null,切斷可達性
在main方法中創建objectA、objectB兩個局部變數,而且相互引用。相互引用直接調System.gc()是回收不了的。而將兩者都置為null,切斷相互引用,切斷了可達性,與GCRoots無引用,那麼這兩個對象就會被回收調。
2.將對象的引用指向另一個對象
這里將one的引用也指向了two引用指向的對象,那麼one原本指向的對象就失去了GCRoots引用,這里就判斷該對象可被回收。
3.局部對象的使用
當方法執行完,局部變數object對象會被判定為可回收對象。
4.只有軟、弱、虛引用與之關聯
new出來的對象被強引用了,就需要去掉強引用,改為弱引用。被弱引用之後,需要置空來幹掉強引用,達到隨時可回收的效果。
只被軟引用的對象在內存不足的情況,可能會被GC回收掉。
只被弱引用持有的對象,隨時都可能被GC回收,該對象就為可回收對象。
是不是被判定為了可回收對象,就一定會被回收了呢。其實Ojbect類中還有一個finalize方法。這個方法是對象在被GC回收之前會被觸發的方法。
該方法翻譯過來就是:當垃圾回收確定不再有對該對象的引用時,由垃圾回收器在對象上調用。子類重寫finalize方法以處置系統資源或執行其他清除。說人話就是對象死前會給你一個迴光返照,讓你清醒一下,想干什麼就干什麼,甚至可以把自己救活。我們可以通過重寫finalize方法,來讓對象復活一下。
示例:
執行的結果:
這里我們重寫FinalizeGC類的finalize方法, 使用FinalizeGC.instance = this語句,讓對象又有了引用,不再被判定為可回收對象,這里就活了。然後再置空再回收一下,這個對象就死了,沒有再被救活了。所以finalize方法只能被執行一次,沒有再次被救活的機會。
在JDK1.8版本廢棄了永久代,替代的是元空間(MetaSpace),元空間與永久代上類似,都是方法區的實現,他們最大區別是:元空間並不在JVM中,而是使用本地內存。
元空間有注意有兩個參數:
MetaspaceSize :初始化元空間大小,控制發生GC閾值
MaxMetaspaceSize : 限制元空間大小上限,防止異常佔用過多物理內存
為什麼移除永久代?
移除永久代原因:為融合HotSpot JVM與JRockit VM(新JVM技術)而做出的改變,因為JRockit沒有永久代。
有了元空間就不再會出現永久代OOM問題了!
1.Generational Collection(分代收集)演算法
分代收集演算法是GC垃圾回收演算法的總綱領。現在主流的Java虛擬機的垃圾收集器都採用分代收集演算法。Java 堆區基於分代的概念,分為新生代(Young Generation)和老年代(Tenured Generation),其中新生代再細分為Eden空間、From Survivor空間和To Survivor空間。 (Survivor:倖存者)
分代收集演算法會結合不同的收集演算法來處理不同的空間,因此在學習分代收集演算法之前我們首先要了解Java堆區的空間劃分。Java堆區的空間劃分在Java虛擬機中,各種對象的生命周期會有著較大的差別。因此,應該對不同生命周期的對象採取不同的收集策略,根據生命周期長短將它們分別放到不同的區域,並在不同的區域採用不同的收集演算法,這就是分代的概念。
當執行一次GC Collection時,Eden空間的存活對象會被復制到To Survivor空間,並且之前經過一次GC Collection並在From Survivor空間存活的仍年輕的對象也會復制到To Survivor空間。
對象進入到From和To區之後,對象的GC分代年齡ege的屬性,每經過GC回收存活下來,ege就會+1,當ege達到15了,對象就會晉級到老年代。
2.Mark-Sweep(標記-清除)演算法
標記清除:標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。標記-清除演算法主要是運用在Eden區,該區對象很容易被回收掉,回收率很高。
3.Copying(復制)演算法
復制演算法的使用在From區和To區,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。
缺點:可使用內存縮減為一半大小。
那麼復制演算法使可使用內存大小會減半,設計上是怎麼解決這個問題的呢。就是給From和To區劃分盡可能小的區域。經過大數據統計之後,對象在第一次使用過後,絕大多數都會被回收,所以能進入第一次復制演算法的對象只佔10%。那麼設計上,Eden、From、To區的比例是8:1:1,絕大多數對象會分配到Eden區,這樣就解決了復制演算法縮減可用內存帶來的問題。
4.Mark-Compact (標記—整理)演算法
在新生代中可以使用復制演算法,但是在老年代就不能選擇復制演算法了,因為老年代的對象存活率會較高,這樣會有較多的復制操作,導致效率變低。標記—清除演算法可以應用在老年代中,但是它效率不高,在內存回收後容易產生大量內存碎片。因此就出現了一種標記—整理演算法,與標記—清除演算法不同的是,在標記可回收的對象後將所有存活的對象壓縮到內存的一端,使它們緊湊地排列在一起,然後對邊界以外的內存進行回收,回收後,已用和未用的內存都各自一邊。
垃圾收集演算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現:
Serial 收集器(復制演算法): 新生代單線程收集器,標記和清理都是單線程,
優點是簡單高效;
Serial Old 收集器 (標記-整理演算法): 老年代單線程收集器,Serial 收集器
的老年代版本;
ParNew 收集器 (復制演算法): 新生代收並行集器,實際上是 Serial 收集器
的多線程版本,在多核 CPU 環境下有著比 Serial 更好的表現;
CMS(Concurrent Mark Sweep)收集器(標記-清除演算法): 老年代並行
收集器,以獲取最短回收停頓時間為目標的收集器,具有高並發、低停頓
的特點,追求最短 GC 回收停頓時間。
❿ JVM 技術詳解:常見的 GC 演算法(Parallel/CMS/G1)
學習了 GC 演算法的相關概念之後,我們將介紹在 JVM 中這些演算法的具體實現。首先要記住的是,大多數 JVM 都需要使用兩種不同的 GC 演算法——一種用來清理年輕代,另一種用來清理老年代。廳行
我們可以選擇 JVM 內置的各種演算法。如果不通過參數明確指定垃圾收集演算法,則會使用相應 JDK 版本的默認實現。本章會詳細介紹各種演算法的實現原理。
串列 GC 對年輕代使用 mark-(標記—復制)演算法,對老年代使用 mark-sweep-compact(標記—清除—整理)演算法。
兩者都是單線程的垃圾收集器,不能進行並行處理,所以都棗型會觸發全線暫停(STW),停止所有的應用線程。
因此這種 GC 演算法不能充分利用多核 CPU。不管有多少 CPU 內核,JVM 在垃圾收集時都只能使用單個核心。
要啟用此款收集器,只需要指定一個 JVM 啟動參數即可,同時對年輕代和老年代生效:
該選項只適合幾百 MB 堆內存的 JVM,而且是單核 CPU 時比較有用。
對於伺服器端來說,因為一般是多個 CPU 內核,並不推薦使用,除非確實需要限制 JVM 所使用的資源。大多數伺服器端應用部署在多核平台上,選擇 串列 GC 就意味著人為地限制了系統資源的使用,會導致資源閑置,多餘的 CPU 資源也不能用增加業務處理凳伏猜的吞吐量。
關於串列垃圾收集器的日誌內容,我們在後面的內容《GC 日誌解讀與分析》之中進行詳細的講解。
並行垃圾收集器這一類組合,在年輕代使用「標記—復制(mark-)演算法」,在老年代使用「標記—清除—整理(mark-sweep-compact)演算法」。年輕代和老年代的垃圾回收都會觸發 STW 事件,暫停所有的應用線程來執行垃圾收集。兩者在執行「標記和復制/整理」階段時都使用多個線程,因此得名「 Parallel 」。通過並行執行,使得 GC 時間大幅減少。
通過命令行參數 -XX:ParallelGCThreads=NNN 來指定 GC 線程數,其默認值為 CPU 核心數。可以通過下面的任意一組命令行參數來指定並行 GC:
並行垃圾收集器適用於多核伺服器,主要目標是增加吞吐量。因為對系統資源的有效使用,能達到更高的吞吐量: