轻gc和重gc分别在什么时候发生_GC发展与现状
GC發展
Java不像C或C++那樣,需要程序員在編程的過程中,時刻注意申請內存保存對象,在對象使用完成后,要在合適的時機將對象占用的內存釋放掉(析構函數);Java得意與內部的三大機制,保證了程序開發方便:
解釋執行+ 即時編譯器JIT,對熱點代碼的即時編譯執行為機器,在方法區中保存代碼緩存;
執行引擎;
垃圾回收機制;
我們今天的重點就是要闡述:垃圾回收機制。
為什么要進行垃圾回收?
原因很簡單:現在的計算機系統都是跑在程序存儲結果的計算機上的,程序必須首先通過IO加載到內存中,才能開始創建程序進程,開始運行。而內存的大小是有上限的,無休止地在內存中創建對象,占用內存,而不去清理,必然是會造成內存不足,導致操作系統將應用進程殺死。
所以就需要java字節碼,要么字節碼中管理內存,申請內存,擴展內存,釋放內存;要么JVM自己提供垃圾回收機制;
要進行垃圾回收,首先就要知道,對象保存在哪里?什么樣的對象是垃圾?
對象保存在哪里?
對象在堆中保存,小的對象可能會保存在棧的TLAB中,JVM的內存區域分五個部分,其實在實際中,java所使用的內存包括有(后續補充)??
java內存 = ?PC寄存器 + java棧 + 本地棧 + 年輕代 +老年代 + 直接內存空間
堆的邏輯劃分
分別介紹如下:
程序計數器PC:是一塊較小的內存空間,作用可以看做是當前線程所執行的字節碼的行號的指示器,線程私有。
JVM方法棧和本地方法棧:在sun的jdk中,JVM方法棧和本地方法棧是算在一起的,虛擬機棧為虛擬機執行java方法,本地方法棧為虛擬機執行Native方法,線程私有。
java heap:是虛擬機中內存區域最大的一塊,細分可以分為新生代和舊生帶,新生代可以劃分為E、S0、S1,其中S1和S0又可以叫做from 或 to,線程共享。
方法區:存放了要加載的類的信息,類中的靜態變量,定義為final的常量,類中的Field信息,方法信息等,全局共享,又叫做持久帶,可以通過 ?-XX:PermSize和-XX:MaxPermSize設置最小值和最大值,線程共享。
各個區的作用
圖示:
新生代:
? ?大多數情況下,java中新建的對象都是在新生代上分配的,新生代由Eden和兩塊相同大小的S0和S1組成,其中S0和S1又稱為From和To(這個劃分沒有先后順序),可以通過-Xmn來設置新生代的大小,-XX:SurvivorRatio設置Eden和S區的比值,有些垃圾回收器會對S0或者S1進行動態的調整。
? ? 之所以說大多數情況下新建的對象在新生代上分配,是因為有兩種情況下java新創建的對象會直接到舊生帶,一種是大的數組對象,且對象中無外部引用的對象,另外一種是通過
啟動參數上面進行設置-XX:PretenureSizeThreshold=1024(單位是字節),意思是對象超過此大小,就直接分配到老年代的堆內存中,此外,并行垃圾回收器可以在運行期決定那些對象可以直接創建在舊生帶。
老年代:
多次回收之后仍然存活的對象,大小是-Xms減去-Xmn。
常見的參數設置:
-XX:+ 啟用選項
-XX:- 不啟用選項
-XX:= 給選項設置一個數字類型值,可跟單位,例如 32k, 1024m, 2g
-XX:= 給選項設置一個字符串值,例如-XX:HeapDumpPath=./dump.core
什么是垃圾對象
Java采用的是可達性分析法。
判斷對象是否為垃圾的方法有兩種:
引用計數法
可達性分析算法
可達性分析算法中,有個非非常常的概念:根集合,引用鏈:
根集合
(1)根集合,包含了一組對象,這些是對運行時數據區的對象快照的掃描,主要包含有:
java虛擬機棧(棧幀中的本地變量表)中的引用的對象
本地方法棧中JNI本地方法的引用對象。
方法區中的類靜態屬性引用的對象
方法區中的常量引用的對象。
常駐的異常對象,鎖對象
其他代的堆內存中,對當前回收內存區域有引用關系的對象;比如:young gc時,出現的老年代中的對象,G1回收時Region中的remember set對象
引用鏈
從根集合出發,開始遍歷堆內存中的對象,在引用鏈上的對象,就是正在使用的對象,不是垃圾;沒有在引用鏈上的對象,就是垃圾,垃圾回收器就要對其進行回收處理。
STW的時機
為了確保堆內存中的對象間的引用關系是不變的,在進行確定根集合時,需要將應用程序暫時停頓,這也是后續垃圾回收器在努力奮斗進行攻克的一個重要參數,停頓時間,越小越好,吞吐量越大越好。
STW的時機:
安全點
哪些時刻作為安全點:
方法開始執行;
方法返回之前;
異常拋出
進入循環調用前
安全區域
哪些時間區域作為安全區域:
sleep()操作
線程阻塞時
垃圾回收算法
三種垃圾回收算法和java最后選擇的回收算法
復制算法
所有的年輕代的收集算法
標記-清除算法
CMS的垃圾回收算法
標記-整理算法
Parallel old的收集算法
Java選擇的垃圾回收算法:分代回收算法,將堆內存進行邏輯劃分為:根據對象的年齡大小,劃分為年輕代,年老代保存,在不同的代,使用不同的回收算法。
垃圾回收器
第一階段 串性回收
用在客戶端模式的java虛擬機上,
暫停應用線程,單線程地回收垃圾對象;
第二階段 并行回收
暫停應用線程,啟動多個GC線程地回收垃圾對象;
對比串性回收的速度高了,停頓的時間變短了,但是仍是在整個回收過程中,只有GC運行,應用線程是不工作的;
第三階段 并發回收
在GC的過程中,根據GC過程的不同階段,將部分階段設置為GC線程和應用線程同時運行,并且最大限度地減少GC造成的暫停時間,這個過程包括有:
收集根對象;
初始標記
并發標記
最終標記
并發清除
舉例:CMS垃圾回收器
第四階段 標記和回收分離
標記和回收都是并發的,并且標記有標記的觸發條件;
回收有回收的觸發條件,兩個條件相互影響。
舉例:G1,ZGC
CMS三個很重要的參數
-XX:CMSInitiatingOccupancyFraction
設置CMS收集器在老年代空間被使用多少(百分比)后觸發垃圾收集。默認設置-XX:CMSInitiatingOccupancyFraction=68表示老年代空間使用比例達到68%時觸發CMS垃圾收集。僅當老年代收集器設置為CMS時候這個參數才有效。
-XX:+UseCMSCompactAtFullCollection
設置CMS收集器在完成垃圾收集后是否要進行一次內存碎片整理。僅當老年代收集器設置為CMS時候這個參數才有效。
-XX:CMSFullGCsBeforeCompaction
設置CMS收集器在進行多少次垃圾收集后再進行一次內存碎片整理。如設置-XX:CMSFullGCsBeforeCompaction=2表示CMS收集器進行了2次垃圾收集之后,進行一次內存碎片整理。僅當老年代收集器設置為CMS時候這個參數才有效。
與日志有關的三個參數:
- -XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-Xloggc
指定gc日志的存放位置。如-Xloggc:/var/log/myapp-gc.log表示將gc日志保存在磁盤/var/log/目錄,文件名為myapp-gc.log
到底Java GC是在什么時候,對什么東西,做了什么事情?
(1)時間:
JVM進程啟動后,就會不斷地在新生代中保存新創建的對象,當Eden區滿了后觸發minor gc,用的是復制算法,當Survivor中的一半以上的對象存活年齡大于平均年齡時,或者對象的年齡大于設置的參數:-XX:MaxTenuringThreshold時,就會觸發對象晉升,從年輕代晉升到老年代保存,當晉升到老年代的對象大小總和大于老年代剩余空間full gc時,并且晉升擔保機制設置為ture時,就會直接嘗試晉升,老年代內存不足時,觸發老年代full gc,或者小于時被HandlePromotionFailure參數強制full gc,gc與非gc的耗時比較后,超過了GCTimeRatio的限制引發OOM:
通過參數NewRatio控制年輕代和老年代的堆空間的比例;
通過參數SurvivorRatio控制年輕代中,伊甸園區和幸存者區的比例
通過參數:-XX:MaxTenuringThreshold設置從年輕代晉升到老年代的對象年齡,其中:CMS是默認:15,G1默認是:6;
GC關鍵技術
三色標記法
白色:還沒標記,或者標記為了垃圾對象;
灰色:標記一半,屬性沒有標記完成
黑色:自己和自己的屬性都已近全部標記完成。
記憶集
用來保存其他堆內存區,對當前GC線程作用的堆內存區的引用關系;
卡表
用來記錄對其他代,或其他Region中的對象,有當前card的引用指向時,就將當前card標記為dirty,并且在垃圾回收時,將dirty保存到drity card queue中;
目的:在進行垃圾回收時,防止對全堆進行掃描,從而降低并發標記或最終標記的暫停時間;
全稱是Remembered Set,是輔助GC過程的一種結構,典型的空間換時間工具,和Card Table有些類似。還有一種數據結構也是輔助GC的:Collection Set(CSet),它記錄了GC要收集的Region集合,集合里的Region可以是任意年代的。在GC的時候,對于old->young和old->old的跨代對象引用,只要掃描對應的CSet中的RSet即可。
邏輯上說每個Region都有一個RSet,RSet記錄了其他Region中的對象引用本Region中對象的關系,屬于points-into結構(誰引用了我的對象)。而Card Table則是一種points-out(我引用了誰的對象)的結構,每個Card 覆蓋一定范圍的Heap(一般為512Bytes)。G1的RSet是在Card Table的基礎上實現的:每個Region會記錄下別的Region有指向自己的指針,并標記這些指針分別在哪些Card的范圍內。這個RSet其實是一個Hash Table,Key是別的Region的起始地址,Value是一個集合,里面的元素是Card Table的Index。
下圖表示了RSet、Card和Region的關系:
SATB
當G1開始進行回收時,為了確保堆內存中,對象之間的引用關系不變,而獲取一個內存快照,進行并發標記的作用對象就是這個SATB內存快照;
CMS和G1算法都涉及對可達對象的并發標記。并發標記的主要問題是collector在標記對象的過程中mutator可能正在改變對象引用關系圖,從而造成漏標和錯標。錯標不會影響程序的正確性,只是造成所謂的浮動垃圾。但漏標則會導致可達對象被當做垃圾收集掉,從而影響程序的正確性。
為解決漏標問題,GC Handbook一書首先將對象分為三類,即所謂的black對象,grey對象和white對象。white對象是那些還沒有被collector標記到的對象;grey對象是那些自身已經被標記到,但其所有引用字段還沒有處理的對象;而black對象則是自身已經被標記到,且其引用的所有對象也已經被標記的對象。
基于上述分類,一個white對象在并發標記階段會被漏標的充分必要條件是:
1、mutator插入了一個從black對象到該white對象的新引用
2、mutator刪除了所有從grey對象到該white對象的直接或者間接引用。
因此,要避免對象的漏標,只需要打破上述2個條件中的任何一個即可。
CMS:
Incremental update關注的是第一個條件的打破,即引用關系的插入。Incremental update利用write barrier將所有新插入的引用關系都記錄下來,最后以這些引用關系的src為根STW地重新掃描一遍即避免了漏標問題。
G1:
SATB關注的是第二個條件的打破,即引用關系的刪除。SATB利用pre write barrier將所有即將被刪除的引用關系的舊引用記錄下來,最后以這些舊引用為根STW地重新掃描一遍即可避免漏標問題。
在G1中,使用的是STAB(snapshot-at-the-beginning)的方式,刪除的時候記錄所有的對象,它有3個步驟:
1,在開始標記的時候生成一個快照圖標記存活對象
2,在并發標記的時候所有被改變的對象入隊(在write barrier里把所有舊的引用所指向的對象都變成非白的)
3,可能存在游離的垃圾,將在下次被收集
G1的寫前屏障
G1在并發標記過程中,當對象與對象之間的引用關系發生變化時,就會將引用斷開的對象,保存起來,在并發階段的后期,重新對這些對象進行掃描標記;
目的:防止對象消失,
CMS的增量更新
CMS在進行并發標記過程中,當對象間的引用關系發生變化時,就會引用發生變化的對象,利用增量更新的條件,使用寫后屏障技術保存起來,在最終標記階段,將會把根集合和并發標記階段引用發生變化的對象為新的根集合,重新掃描一遍所有的對象;
目的:防止對象消失;
缺點:可能存在浮動垃圾;
染色指針
多個虛擬內存地址,根據地址中特殊位置的標記位,用來表示哪個地址段是有效的,從而實現多個虛擬內存地址,對應一個物理內存地址,可支持最大內存空間為:
16T;
G1的標記過程
概述
STAB全稱Snapshot-At-The-Beginning,由字面理解,是GC開始時活著的對象的一個快照。它是通過Root Tracing得到的,作用是維持并發GC的正確性。那么它是怎么維持并發GC的正確性的呢?根據三色標記算法,我們知道對象存在三種狀態:
白:對象沒有被標記到,標記階段結束后,會被當做垃圾回收掉,即灰色節點的子節點。
灰:對象被標記了,但是它的field還沒有被標記或標記完。
黑:對象被標記了,且它的所有field也被標記完了。
由于并發階段的存在,那就有可能在并行運行期間之前的標記過的對象的引用關系可能被改變,就會出現白對象漏標的情況,這種情況發生的前提是:
把一個白對象的引用存到黑對象的字段里,如果這個情況發生,因為標記為黑色的對象認為是掃描完成的,不會再對它進行掃描。
某個白對象失去了所有能從灰對象到達它的引用路徑。
對于第一個條件,在并發標記階段,如果該白對象是new出來的,并沒有被灰對象持有,那么它會不會被漏標呢?
如果灰對象到白對象的直接引用或者間接引用被替換了,或者刪除了,白對象就會被漏標,從而導致被回收掉,這是非常嚴重的錯誤。
解決新創建對象產生的漏標問題
??????? SATB算法機制中,會在GC開始時先創建一個對象快照,在并發標記時所有快照中當時的存活對象就認為是存活的,標記過程中新分配的對象也會被標記為存活對象,不會被回收。這種機制能夠很好解決新創建對象漏標的情況。STAB核心的兩個結構就是兩個Bitmap。
?????? Bitmap分別存儲在每個Region中,并發標記過程里的兩個重要的變量:preTAMS(pre-top-at-mark-start,代表著Region上一次完成標記的位置) 以及nextTAMS(next-top-at-mark-start,隨著標記的進行會不斷移動,一開始在top位置)。SATB通過控制兩個變量的移動來進行標記,移動規則如下:
假設第n輪并發標記開始,將該Region當前的Top指針賦值給nextTAMS,在并發標記標記期間,分配的對象都在[ nextTAMS, Top ]之間,SATB能夠確保這部分的對象都會被標記,默認都是存活的。
當并發標記結束時,將nextTAMS所在的地址賦值給previousTAMS,SATB給[ Bottom, previousTAMS ]之間的對象創建一個快照Bitmap,所有垃圾對象能通過快照被識別出來。
第n+1輪并發標記開始,過程和第n輪一樣。
A階段,初始標記階段,需要STW,將掃描Region的Top值賦值給nextTAMS。
A-B階段:并發標記階段。
B階段,并發標記結束階段,此時并發標記階段生成的新對象都會被分配在[nextTAMS,Top]之間,這些對象會被定義為“隱式對象”,同時_next_mark_bitmap也開始存儲nextTAMS標記的對象的地址。
C階段,清除階段,_next_mark_bitmap和_prev_mark_bitmap會進行交換,同時清理[ Bottom, previousTAMS ]之間被標記的所有對象,對于“隱式對象”會在下次垃圾收集過程進行回收(如第F步),這也是SATB存在弊端,會一定程度產生未能在本次標記中識別的浮動垃圾。
解決對象引用被修改產生的漏標問題
??????? SATB利用pre-write barrier,將所有即將被修改引用關系的白對象舊引用記錄下來,最后以這些舊引用為根重新掃描一遍,以解決白對象引用被修改產生的漏標問題。
在引用修改時把原引用保存到satb_mark_queue中,每個線程都自帶一個satb_mark_queue。在下一次的并發標記階段,會依次處理satb_mark_queue中的對象,確保這部分對象在本輪GC中是存活的。
????????如果被修改引用的白對象就是要被收集的垃圾,這次的標記會讓它躲過GC,這就是float garbage。因為SATB的做法精度比較低,所以造成的float garbage也會比較多。
總結
?本文主要對GC發展做了詳述,分布式高并發的環境,造就了GC不斷發展,要在最短的時間內,給用戶響應最多的返回數據,就需要后端服務有很強的響應能力;
就需要GC的暫停時間段,GC回收的效率越高越好。
總結
以上是生活随笔為你收集整理的轻gc和重gc分别在什么时候发生_GC发展与现状的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 可口可乐凉茶取名夏枯草卖12一瓶遭吐槽!
- 下一篇: pyqt5设置dialog的标题_PyQ