gcroot是怎麼存儲的
Ⅰ JVM垃圾回收的「三色標記演算法」實現,內容太干
三色標記法是一種垃圾回收法,它可以讓JVM不發生或僅短時間發生STW(Stop The World),從而達到清除JVM內存垃圾的目的。JVM中的 CMS、G1垃圾回收器 所使用垃圾回收演算法即為三色標記法。
三色標記法將對象的顏色分為了黑、灰、白,三種顏色。
白色 :該對象沒有被標記過。(對象垃圾)
灰色 :該對象已經被標記過了,但該對象下的屬性沒有全被標記完。(GC需要從此對象中去尋找垃圾)
黑色 :該對象已經被標記過了,且該對象下的屬性也全部都被標記過了。(程序所需要的對象)
從我們main方法的根對象(JVM中稱為GC Root)開始沿著他們的對象向下查找,用黑灰白的規則,標記出所有跟GC Root相連接的對象,掃描一遍結束後,一般需要進行一次短暫的STW(Stop The World),再次進行掃描,此時因為黑色對象的屬性都也已經被標記過了,所以只需找出灰色對象並順著繼續往下標記(且因為大部分的標記工作已經在第一次並發的時候發生了,所以灰色對象數量會很少,標記時間也會短很多), 此時程序繼續執行,GC線程掃描所有的內存,找出掃描之後依舊被標記為白色的對象(垃圾),清除。
具體流程:
在JVM虛擬機中有兩種常見垃圾回收器使用了該演算法:CMS(Concurrent Mark Sweep)、G1(Garbage First) ,為了解決三色標記法對對象漏標問題各自有各自的法:
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用集中在互聯網網站或者基於瀏覽器的B/S系統的服務端上,這類應用通常都會較為關注服務的響應速度,希望系統停頓時間盡可能短,以給用戶帶來良好的交互體驗。CMS收集器就非常符合這類應用的需求(但是實際由於某些問題,很少有使用CMS作為主要垃圾回收器的)。
從名字(包含「Mark Sweep」)上就可以看出CMS收集器是基於標記-清除演算法實現的,它的運作過程相對於前面幾種收集器來說要更復雜一些,整個過程分為四個步驟,包括:1)初始標記(CMS initial mark) 2)並發標記(CMS concurrent mark) 3)重新標記(CMS remark) 4)並發清除(CMS concurrent sweep)
其中初始標記、重新標記這兩個步驟仍然需要「Stop The World」。初始標記僅僅只是標記一下GCRoots能直接關聯到的對象,速度很快;
並發標記階段就是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以與垃圾收集線程一起並發運行;
重新標記階段則是為了修正並發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但也遠比並發標記階段的時間短;
最後是並發清除階段,清理刪除掉標記階段判斷的已經死亡的對象,由於不需要移動存活對象,所以這個階段也是可以與用戶線程同時並發的。由於在整個過程中耗時最長的並發標記和並發清除階段中,垃圾收集器線程都可以與用戶線程一起工作,所以從總體上來說,CMS收集器的內存回收過程是與用戶線程一起並發執行的。
在應對漏標問題時,CMS使用了增量更新(Increment Update)方法來做:
在一個未被標記的對象(白色對象)被重新引用後, 引用它的對象若為黑色則要變成灰色,在下次二次標記時讓GC線程繼續標記它的屬性對象 。
但是就算是這樣,其仍然是存在漏標的問題:
G1(Garbage First)物理內存不再分代,而是由一塊一塊的Region組成,但是邏輯分代仍然存在。G1不再堅持固定大小以及固定數量的分代區域劃分,而是把連續的Java堆劃分為多個大小相等的獨立區域(Region),每一個Region都可以根據需要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。收集器能夠對扮演不同角色的Region採用不同的策略去處理,這樣無論是新創建的對象還是已經存活了一段時間、熬過多次收集的舊對象都能獲取很好的收集效果。
Region中還有一類特殊的Humongous區域,專門用來存儲大對象。G1認為只要大小超過了一個Region容量一半的對象即可判定為大對象。每個Region的大小可以通過參數-XX:G1HeapRegionSize設定,取值范圍為1MB~32MB,且應為2的N次冪。而對於那些超過了整個Region容量的超級大對象,將會被存放在N個連續的Humongous Region之中,G1的大多數行為都把Humongous Region作為老年代的一部分來進行看待,如圖所示
Card Table(多種垃圾回收器均具備)
RSet(Remembered Set)
是輔助GC過程的一種結構,典型的空間換時間工具,和Card Table有些類似。
後面說到的CSet(Collection Set)也是輔助GC的,它記錄了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。每個Region中都有一個RSet,記錄其他Region到本Region的引用信息;使得垃圾回收器不需要掃描整個堆找到誰引用當前分區中的對象,只需要掃描RSet即可。
CSet(Collection Set)
一組可被回收的分區Region的集合, 是多個對象的集合內存區域。
新生代與老年代的比例
5% - 60%,一般不使用手工指定,因為這是G1預測停頓時間的基準,這地方簡要說明一下,G1可以指定一個預期的停頓時間,然後G1會根據你設定的時間來動態調整年輕代的比例,例如時間長,就將年輕代比例調小,讓YGC盡早行。
SATB(Snapshot At The Beginning), 在應對漏標問題時,G1使用了SATB方法來做,具體流程:
因為SATB在重新標記環節只需要去重新掃描那些被推到堆棧中的引用,並配合Rset來判斷當前對象是否被引用來進行回收;
並且在最後G1並不會選擇回收所有垃圾對象,而是根據Region的垃圾多少來判斷與預估回收價值(指回收的垃圾與回收的STW時間的一個預估值),將一個或者多個Region放到CSet中,最後將這些Region中的存活對象壓縮並復制到新的Region中,清空原來的Region。
會,當內存滿了的時候就會進行Full GC;且JDK10之前的Full GC,為單線程的,所以使用G1需要避免Full GC的產生。
解決方案:
Ⅱ 對象和內存溢出怎麼處理
1. 對象。
A.創建。首先檢查指令的參數能不能在常量區找到類的符號引用,並檢查這個類是否載入、解析和初始化過,如果沒有就執行類的載入過程。其次是內存分配,類載入之後就知道要分配的內存大小,分配方法有兩種,一種是指針碰撞,就是一塊內存是使用過的,一塊是未使用的,用一個指針分割,新分配的內存指針就向空閑的挪動,compact功能的虛擬機是用指針碰撞;另一種是空閑列表,就是一個列表記錄空閑的內存塊,不斷更新列表,新分配的內存在列表中尋找一個合適大小的內存塊,sweep功能的虛擬機是使用空閑列表。第三,在分配內存空間的時候,還要考慮並發性。有兩個方法,一種是同步處理,如採用CAS和失敗重試的方法;另外一種是把內存分配動作按照線程劃分在不同的空間之中,每個線程在堆中預先分配一小塊內存,本地線程分配緩沖TLAB,那個線程需要分配內存在那個TLAB上分配,只有TLAB用完了,才要同步鎖定,重新分配。第四、對對象進行必要設置,比方說對象屬於那個類,如何找到類的元數據信息和對象hashcode以及對象GC分代年齡等。
B.對象的內存布局。分為對象頭、實例數據和對齊填充。對象頭包括兩部分,第一部分是存儲對象自身信息,如hashcode,GC分代年齡,鎖狀態等;第二部分是類型指針,對象指向它的類的元數據的指針,虛擬機通過這個指針確定這是那個類的實例。
C.對象訪問定位。兩種方式,一種是句柄訪問,句柄池有訪問對象實例數據的指針和訪問對象數據類型的指針。這個訪問最大好處是reference是穩定的句柄池地址,對象改變都是改變句柄池裡面的指針,而reference本身不動。另外一種就是直接指針,它有到對象類型數據的指針和實例數據。這個訪問的好處是速度更快,節省了一次指針定位的開銷。
2. 內存溢出OOM。
A.堆溢出。堆存放的是對象實例,只要不斷創建對象,並且保證GC Root到對象有可大路徑避免被垃圾回收清除掉對象,那麼對象數量達到最大堆容量限制就會OOM。用內存映象分析工具,Eclipse Memory Analyzer分析一下。
B.虛擬機棧和本地方法棧溢出。分為兩種,一種是如果線程請求的棧深度大於虛擬機所允許的最大深度,拋出StackOverFlowError異常;另一種是如果虛擬機在擴展棧時無法申請到足夠內存空間,拋出OutOfMemoryError異常。可以減小最大堆和棧容量來獲取更多的線程數量。
C.方法區和常量池溢出。會有額外提示 PermGen space。
D.本機直接內存溢出。這個Heap Dump文件看不到內存佔用,但是如果有直接或簡介使用了NIO,那有可能就是本機直接內存溢出了。
Ⅲ jvm如何gc,新生代,老年代,持久代,都存儲哪些東西
虛擬機中共劃分為三個代:年輕代(即新生代)、年老代和持久代。
持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關系不大。
年輕代和年老代的劃分是對垃圾收集影響比較大的。
所有新生吵行虧成的對象首先都是放在年輕代的
年老代中存放的都是一些生命周期較長的對象。
持久代:用於存放靜態文件,升神如今Java類、方法等帶森。持久代對垃圾回收沒有顯著影響。
Ⅳ majorgc觸發條件
Major GC通常是跟full GC是等價的,收集整個GC堆。但因為HotSpot VM發展了這么多年,外界對各種名詞的解讀已經完全混亂了,當有人說「major GC」的時候一定要問清楚他想要指的是上面的full GC還是old gen。
最簡單的分代式GC策略,按HotSpot VM的serial GC的實現來看,觸發條件是:
young GC:當young gen中的eden區分配滿的時候觸發。注意young GC中有部分存活對象會晉升到old gen,所以young GC後old gen的佔用量通常會有所升高。
full GC:當准備要觸發一次young GC時,如果發現統計數據說之前young GC的平均晉升大小比目前old gen剩餘的空間大,則不會觸發young GC而是轉為觸發full GC(因為HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都會同時收集整個GC堆,包括young gen,所以不需要事先觸發一次單獨的young GC);或者,如果有perm gen的話,要在perm gen分配空間但已經沒有足夠空間時,也要觸發一次full GC;或者System.gc()、heap mp帶GC,默認也是觸發full GC。
HotSpot VM里其它非並發GC的觸發條件復雜一些,不過大致的原理與上面說的其實一樣。
當然也總有例外。Parallel Scavenge(-XX:+UseParallelGC)框架下,默認是在要觸發full GC前先執行一次young GC,並且兩次GC之間能讓應用程序稍微運行一小下,以期降低full GC的暫停時間(因為young GC會盡量清理了young gen的死對象,減少了full GC的工作量)。這是HotSpot VM里的奇葩嗯。
並發GC的觸發條件就不太一樣。以CMS GC為例,它主要是定時去檢查old gen的使用量,當使用量超過了觸發比例就會啟動一次CMS GC,對old gen做並發收集。