垃圾回收的總結

1、可達性與引用html

GCRoots:算法

虛擬機棧中引用的對象安全

方法區中類靜態屬性引用的對象數據結構

方法區中常量引用的對象併發

本地方法棧中JNI引用的對象性能

 

強引用:最多見的引用優化

軟引用:系統內存要溢出了回收spa

弱引用:對象未被引用就收集了.net

虛引用:被找到就直接收集掉線程

 

finalize方法:

宣告一個對象死亡至少有經歷兩次標記

第一次標記,判斷他是否有必要finalize(),若對象未被覆蓋finalize()方法或被虛擬機執行過了,則認爲不必執行;如有必要執行會被加入F-Queue隊列中,GC稍後再標記一次。

每一個對象finalize方法只能執行一次,由於自救只能救一次

 

2、垃圾收集算法與收集器

標記-清除

複製-清除 

標記-整理

新生代空間大,GC後存活量少,因此多數用複製;老年代空間小,存活率高,沒有分配擔保,用標記

 

GC須要再某個一致性的快照(彷彿系統被凍結的時間點)進行分析,所以必需要作Stop The World。HotSpot中經過OopMap數據結構記錄(待補完)

 虛擬機在一些特定指令位置設置一些「安全點」,當程序運行到這些「安全點」的時候就會暫停全部當前運行的線程(Stop The World 因此叫STW),暫停後再找到「GC Roots」進行關係的組建,進而執行標記和清除。 
  這些特定的指令位置主要在:

一、循環的末尾
二、方法臨返回前 / 調用方法的call指令後
三、可能拋異常的位置

 

收集器

年輕代:

a.串行 Serial,串行執行,Stop The World,JDK 1.3.1前惟一選擇,單CPU甚至雙CPU下比並行要好,

b. 並行 ParNew,並行執行,Stop The World

c. Parallel Scavenge,並行執行,經過參數 -XX:MaxGCPauseMillis控制最大停頓時間,-XX:GCTimeRatio控制吞吐量。但他並不能和CMS配合

老年代

d. Serial Old,做爲CMS的後備,當CMS浮動垃圾太多而Concurrent Mode Failure時

e. Parallel Old

f. CMS :標記——清除,將垃圾回收的過程分爲4個步驟

初始標記:Stop The World,僅標記可關聯對象

1. 標記老年代中全部的GC Roots對象
2. 標記年輕代中活着的對象引用到的老年代的對象(指的是年輕帶中還存活的引用類型對象,引用指向老年代中的對象)

ps:爲了加快此階段處理速度,減小停頓時間,能夠開啓初始標記並行化,-XX:+CMSParallelInitialMarkEnabled,同時調大並行標記的線程數,線程數不要超過cpu的核數;

併發標記:耗時長,與用戶線程一塊兒跑

從「初始標記」階段標記的對象開始找出全部存活的對象;

由於是併發運行的,在運行期間會發生新生代的對象晉升到老年代、或者是直接在老年代分配對象、或者更新老年代對象的引用關係等等,對於這些對象,都是須要進行從新標記的,不然有些對象就會被遺漏,發生漏標的狀況。爲了提升從新標記的效率,該階段會把上述對象所在的Card標識爲Dirty,後續只需掃描這些Dirty Card的對象,避免掃描整個老年代; 
併發標記階段只負責將引用發生改變的Card標記爲Dirty狀態,不負責處理

這個階段由於是併發的容易致使concurrent mode failure

 

預清理

 

經過參數CMSPrecleaningEnabled選擇關閉該階段,默認啓用,主要作兩件事情:

處理新生代已經發現的引用,好比在併發階段,在Eden區中分配了一個A對象,A對象引用了一個老年代對象B(這個B以前沒有被標記),在這個階段就會標記對象B爲活躍對象。

在併發標記階段,若是老年代中有對象內部引用發生變化,會把所在的Card標記爲Dirty(其實這裏並不是使用CardTable,而是一個相似的數據結構,叫ModUnionTalble),經過掃描這些Table,從新標記那些在併發標記階段引用被更新的對象(晉升到老年代的對象、本來就在老年代的對象)

 

可中斷的預清理 AbortablePreclean

該階段發生的前提是,新生代Eden區的內存使用量大於參數CMSScheduleRemarkEdenSizeThreshold 默認是2M,若是新生代的對象太少,就沒有必要執行該階段,直接執行從新標記階段

由於CMS GC的終極目標是下降垃圾回收時的暫停時間,因此在該階段要盡最大的努力去處理那些在併發階段被應用線程更新的老年代對象,這樣在暫停的從新標記階段就能夠少處理一些,暫停時間也會相應的下降。

在該階段,主要循環的作兩件事:

  1. 處理 From 和 To 區的對象,標記可達的老年代對象
  2. 和上一個階段同樣,掃描處理Dirty Card中的對象

 

從新標記:Stop The World

這個階段會致使第二次stop the word,該階段的任務是完成標記整個年老代的全部的存活對象。 
這個階段,從新標記的內存範圍是整個堆,包含_young_gen和_old_gen。爲何要掃描新生代呢,由於對於老年代中的對象,若是被新生代中的對象引用,那麼就會被視爲存活對象,即便新生代的對象已經不可達了,也會使用這些不可達的對象當作cms的「gc root」,來掃描老年代; 所以對於老年代來講,引用了老年代中對象的新生代的對象,也會被老年代視做「GC ROOTS」:當此階段耗時較長的時候,能夠加入參數-XX:+CMSScavengeBeforeRemark,在從新標記以前,先執行一次ygc,回收掉年輕帶的對象無用的對象,並將對象放入倖存帶或晉升到老年代,這樣再進行年輕帶掃描時,只須要掃描倖存區的對象便可,通常倖存帶很是小,這大大減小了掃描時間 

併發清除:開始清除,耗時長,與用戶線程一塊兒跑,產生浮動垃圾

 

CMS問題:

對CPU敏感:CMS默認線程數是(CPU數 + 3)/4 ,就是4個以上時收集器有25%以上的cpu資源,當CPU不足四個時就會影響用戶程序。有種增量式併發收集器i-CMS,經過搶佔式來處理,使得垃圾收集時間更長,但對用戶程序影響小點

浮動垃圾:併發清理時用戶線程在跑,就可能有浮動垃圾,使得CMS不能像其餘收集器那樣老年代幾乎滿了再收集。1.5默認用了68%就會激活,1.6是92%,經過-XX:CMSInitiatingOccupancyFraction的值控制百分比。CMS運行期預留內存沒法知足程序須要,就會Concurrent Mode Failure,就是啓動預備的Serial Old,再不行就Full GC

內存碎片:由於標記清除致使內存碎片。CMS提供一個-XX:+UseCMSCompactAtFullConllection開關,決定FullGC前作不作壓縮(STW),還有一個-XX:CMSFullGCsBeforeCompaction 決定多少次不壓縮的Full GC後來一次壓縮

 

G1 收集器

新的Garbage-First(G1)GC就回到了以copying爲基礎的算法上,把整個GC堆劃分爲許多小區域(region),經過每次GC只選擇收集不多量region來控制移動對象帶來的暫停時間。這樣既能實現低延遲也不會受碎片化的影響

(待補充)

 

3、內存分配與回收策略

主要分配在Eden,若是啓動了本地線程分配緩衝,按線程優先在TLAB上。

Eden沒有足夠空間使進行一次Minor GC

大對象直接進入老年代,長期存活的對象進入老年代。每一個對象有一個Age計數器,Eden中通過一次GC仍然存活並能被Survivor容納,移動到Survivor,年齡記爲1,而後每次Minor GC對象加一,默認15次到老年代(參數 -XX:MaxTenuringThreshold)

注:年齡判斷不是絕對的,Survivor空間中相同年齡全部對象大小大於Survivor的一半時,年齡大於等於這個的對象就直接進入老年代

 

空間分配擔保

Minor GC前檢查老年代最大可用連續空間十分大於新生代全部對象總空間,成立則絕對安全;

不然,查看虛擬機參數HandlePromotionFailure是否容許擔保失敗,若是容許,查詢老年代最大可用連續空間是否大於歷次晉升老年代對象的平均大小,大於則試一把Minor GC;若小於且不容許擔保,則Full GC

 

4、問題總結

一、爲何有兩個Survivor

設置兩個Survivor區最大的好處就是解決了碎片化

爲何一個Survivor區不行?第一部分中,咱們知道了必須設置Survivor區。假設如今只有一個survivor區,咱們來模擬一下流程: 
剛剛新建的對象在Eden中,一旦Eden滿了,觸發一次Minor GC,Eden中的存活對象就會被移動到Survivor區。這樣繼續循環下去,下一次Eden滿了的時候,問題來了,此時進行Minor GC,Eden和Survivor各有一些存活對象,若是此時把Eden區的存活對象硬放到Survivor區,很明顯這兩部分對象所佔有的內存是不連續的,也就致使了內存碎片化。 
碎片化帶來的風險是極大的,嚴重影響JAVA程序的性能。堆空間被散佈的對象佔據不連續的內存,最直接的結果就是,堆中沒有足夠大的連續內存空間,接下去若是程序須要給一個內存需求很大的對象分配內存。。。畫面太美不敢看。。。這就比如咱們登山的時候,揹包裏全部東西緊挨着放,最後就可能省出一塊完整的空間放相機。若是每件行李之間隔一點空隙亂放,極可能最後就要一路把相機掛在脖子上了。

那麼,瓜熟蒂落的,應該創建兩塊Survivor區,剛剛新建的對象在Eden中,經歷一次Minor GC,Eden中的存活對象就會被移動到第一塊survivor space S0,Eden被清空;等Eden區再滿了,就再觸發一次Minor GC,Eden和S0中的存活對象又會被複制送入第二塊survivor space S1(這個過程很是重要,由於這種複製算法保證了S1中來自S0和Eden兩部分的存活對象佔用連續的內存空間,避免了碎片化的發生)。S0和Eden被清空,而後下一輪S0與S1交換角色,如此循環往復。上述機制最大的好處就是,整個過程當中,永遠有一個survivor space是空的,另外一個非空的survivor space無碎片。

 

二、Full GC的觸發條件與優化

(1)調用System.gc時,系統建議執行Full GC,可是沒必要然執行

(2)老年代空間不足

(3)方法去空間不足

(4)經過Minor GC後進入老年代的平均大小大於老年代的可用內存

(5)由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小

避免Full GC : https://blog.csdn.net/endlu/article/details/51144918

 

三、jdk 1.8 JVM的更新

https://www.cnblogs.com/sxdcgaq8080/p/7156227.html

相關文章
相關標籤/搜索