java中的垃圾回收機制簡介

內存空間是有限的,運行時若是不能獲取到內存,會拋出OutOfMemory,一種有效的解決措施是,拋棄那些程序永遠不會再也不用到的對象,騰出空間。html

如何定義對象不會用到

  1. 給對象添加一個引用計數器,每當這個對象被引用一次就加1,每當這個對象的引用失效1次,就減1,那麼引用次數爲0的就沒有再用了,非0就表明還有用,可是引用計數器很難解決循環引用java

    新建對象的引用計數爲1,若是兩個新建對象互相引用,那麼他們的引用計數爲2,此時若是隻將原新對象置爲null,只會各自使得引用計數減1,這種場景下獲得的結果引用結果是1,於是僅靠這種粗略的檢查並不能達到一個好的效果算法

  2. 給對象的引用作追蹤。能夠定義一組集合,認定從這個集合出發,可以追溯到的全部對象,都是可用的,其他的都是不可用的安全

    這種集合也稱做 GC Roots,它定義一組根引用,包括當前全部正在被調用的方法的引用類型參數、局部變量、臨時值;方法區中的常量引用對象;本地方法棧中的JNI等等bash

可引用對象的細分

幹掉沒有引用的對象,沒什麼問題,可是若是內存空間仍然不夠,能夠幹掉部分雖然可用,可是不那麼重要的對象來「確保大局」,java對此細分了強引用、軟引用、弱引用、虛引用多線程

詳見reference 引用併發

經常使用的垃圾回收算法思想

  1. 標記-清除。首先標記出須要清除的對象,而後再統一清除。
  2. 複製算法。將一塊內存分爲兩半,每次只用一半,當對使用的這塊內存回收時,將能夠用的對象複製到另外一塊內存上,而後一次性清除全部使用過的內存空間
  3. 標記-整理。思想與標記-清除一致,只是在清除以前,會先把全部存活的對象都移向一端,而後清掉端邊界外的內存
  4. 分代思想。根據對象的存活週期將內存劃分紅幾塊,對不一樣的存活時間使用不一樣的垃圾回收算法

分代GC帶來的好處

  1. 大多數狀況下,數據都會知足這麼一個假設:大部分對象的存活時間很短,而其它的對象則有可能存活時間很長。在這一假設前提下劃分爲年輕代和老年代。配置年輕代佔據堆中較小的一塊,新建立的對象都在年輕代裏面,GC時,因爲大部分對象都會消亡,只會留下較小的部分,這樣適合使用複製算法的思想來處理,這樣的劃分便下降了單次GC的時間長度(遍歷的空間少),同時提升了GC的效率(回收的多)。hotspot中年輕代被劃分紅8:1:1,這裏其實就是認爲90%的對象都會被回收,10%用來保留活下來的,這也就意味着複製算法每次只有10%的空間浪費
  2. 爲了更快的釋放空間,一邊能處理應用內存的分配。 併發GC的本質是GC一邊蒐集,應用一邊產生,若是GC的速度跟不上產生的速度,那麼垃圾就會組件堆積,最終應用分配請求只能停下來等GC追趕,所以越快釋放出越多的空間,就能越好應付越高的應用分配內存的速率,從而讓GC以完美的併發模式工做。

GC爲何要分代eclipse

何時能夠回收

要作回收,首先得知道哪些對象是可達的(存活的),而要知道可達性,對於對象引用追蹤這種思想,就得要去遍歷整個GC根集合。而要作到精準的枚舉,就須要知道哪些棧的槽位有引用,哪些寄存器有引用,於是須要有一些位置去保存這些信息,而可以保存這些信息的地方即 安全點或者安全區域jsp

可以保存這些信息的地方一定也是知道引用狀況的地方,這些地方也就能夠執行GCpost

hotspot中的垃圾收集器

不管使用哪一種收集器,在收集開始的時候都是從 safepoint開始

serial年輕代收集器

"古老"的收集器,使用單線程收集,它工做時必須暫停全部用戶的線程,直到收集結束。對於年輕代的收集則使用複製算法。 能夠用於Client模式下的虛擬機

ParNew年輕代收集器

serial的多線程版本。多線程收集,它工做時必須暫停全部用戶的線程,直到收集結束。對於年輕代的收集則使用複製算法。與CMS收集器配合工做,使用 -XX:UseConcMarkSweepGC的默認年輕代收集器

Parallel Scavenge年輕代收集器

多線程收集器。它的目標是提供一個可控的吞吐量:

吞吐量=運行用戶代碼的時間/(運行用戶代碼的時間+垃圾收集的時間)

與縮短停頓時間的收集器相比,它的目標是高效率的利用CPU的時間,儘快完成運算任務。另外它還支持自適應調節:好比年輕代大小、Eden和Survior的比例、晉升老年代的大小等,來達到最佳的吞吐量。適合後臺運算而不須要太多交互的任務,不能配合CMS工做

縮短停頓時間的關注點則是在於提供良好的響應速度,從而提高用戶體驗

Serial老年代收集器

單線程收集。它須要暫停全部用戶線程,直到收集結束。年老代使用標記-整理算法。它一樣適用於Client模式下的虛擬機

若是是Server模式,在JDK1.5以及以前能夠用來配合Parallel Scavenge搭配使用,以及做爲CMS收集器的預備方案,在發生Concurrent Mode Failure時使用

Parallel 老年代收集器

多線程收集。它須要暫定全部用戶線程,直到收集結束。使用標記整理算法,它也是以吞吐量優先,在JDK1.6中提供,用來配合Parallel Scavenge使用

CMS(Concurrent Mark Sweep) 老年代收集器

併發收集。它分爲4個階段:

  • 初始標記:須要暫停用戶線程。用於標記GC Roots能直接關聯到的對象
  • 併發標記:不需暫停用戶線程。用於標記全部活着的對象
  • 從新標記:須要暫停用戶線程。修正由於用戶線程運行而致使的標記變更的對象
  • 併發清除:不需暫停用戶線程。清除消亡的對象

它總體使用的是標記-清除 算法,系統停頓時間短,適用響應速度快,用戶體驗好
缺點:

  1. 併發意味着它會佔用CPU資源,吞吐量就低;
  2. 因爲清理的時候用戶線程還在運行,那麼用戶線程也是須要空間的,若是空間不夠,就產生Concurrent Mode Failure,轉而使用Serial Old,另外用戶運行也會不斷的產生垃圾,這部分沒法清除(浮動垃圾),

    所以有設置CMS觸發的參數 -XX:CMSInitiatingOccupancyFraction

  3. 標記-清除以後會產生碎片

    能夠經過 -XX:CMSCompactAtFullCollection設置是否要清理碎片,以及 -XX:CMSFullGCsBeforeCompaction來表示多這次運行不壓縮的Full GC後來一次壓縮

G1(Garbage-First)收集器

新生代和老年代均可以收集。大體步驟以下:

  1. 初始標記:須要暫停用戶線程。用於標記GC Roots能直接關聯到的對象,以及實現G1算法相關的操做
  2. 併發標記:不準須要暫停用戶線程。用於標記活着的對象
  3. 最終標記:須要停頓並行執行。用於修正因用戶運行而致使的標記變動
  4. 篩選回收:不須要暫停,併發執行。根據G1算法的細節進行回收價值和成本的排序,生成執行計劃

與CMS相比優勢:

  • 空間整合:G1從總體上來說是基於標記-整理算法,從局部來說是基於「複製」算法實現,這意味着運行期間不會產生內存空間碎片
  • 可預測的停頓:使用者能夠指定在長度爲M毫秒的時間片斷內,消耗時間不超過N毫秒

JDK 7 引入

ZGC

併發垃圾收集器。幾乎全部的階段都是併發執行

ZGC仍然會壓縮堆,壓縮堆這件事,一般意味着

  • 將或者的對象移到堆的一端
  • 執行移動過程當中須要暫停應用線程

壓縮主要會遇到這麼些問題

  • 在搬運對象到另外一個內存地址的時候,另外一個線程也同時會對對象進行讀和寫
  • 搬運成功後,其它有這個對象引用的也必須去跟新他們的引用地址

ZGC過程地址

JDK 11引入

垃圾收集器參數

  • UseParNewGC:使用ParNew+Serial Old收集器
  • UseConcMarkSweepGC:使用ParNew+CMS+Serial Old收集器
  • UseParallelGC:使用Parallel Scavenge+Serial Old
  • UseParallelOldGC:使用Parallel Scavenge+Parallel Old

運行調節的參數

  • SurvivorRatio:Eden與Survivor的分配比例
  • Newratio:年輕代和年老代的比值,好比4表示young:old=1:4
  • PretenureSizeThreshold:直接晉升到老年代的對象大小
  • MaxTenuringThreshold:晉升到老年代的年齡
  • ParallelGCThreads:並行GC內存回收的線程數
  • CMSCompactAtFullCollection:運行CMS後是否須要碎片整理
  • CMSFullGCsBeforeCompaction:運行CMS若干次後再進行碎片整理
  • CMSInitiatingOccupancyFraction:使用CMS,老年代空間使用多少後觸發GC,默認68%

專業名詞

名字講解地址

Partial GC:不收集整個GC堆

  • young GC:只收集年輕代
  • Old GC:只收集年老代,限CMS的並行收集
  • Mixed GC:收集年輕代和年老代,限G1

Full GC:收集整個堆,包括年輕代,年老代,永久帶(若是有的話)

Minor GC通常指的是young GC;Major GC一般和Full GC等價,另外因爲名詞混用,也可能指的是Old GC

觸發young gc的時候,若是發現以前young GC的平均大小比目前老年代的剩餘空間大,則觸發Full GC,永久帶若是沒有足夠的空間,也會觸發Full GC

注意: ParallelScavenge 則是在每次觸發Full GC以前會先執行一次young gc,再執行full gc;

GC 文件

使用 jstat -gc pid time_interval count格式可以查看Java堆情況

  • gcutil能夠用來查詢百分比
  • gcnew/gcold分別查看年輕代和年老代的GC

結果以下

S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
16960.0 16960.0 5116.0  0.0   136064.0 93854.9   339724.0   271888.9  152936.0 149578.7 20444.0 19683.3    220    2.122  19      0.923    3.045
複製代碼
  • S0C、S1C、S0U、S1U:Survivor 0/1區容量(Capacity)和使用量(Used)
  • EC、EU:Eden區容量和使用量
  • OC、OU:年老代容量和使用量
  • PC、PU:永久代容量和使用量
  • YGC、YGT:年輕代GC次數和GC耗時
  • FGC、FGCT:Full GC次數和Full GC耗時
  • GCT:GC總耗時

使用 -XX:+PrintGCDetails 能夠顯示GC的狀況,形如

[GC[ParNew: 6996K->1202K(78656K), 0.0036460 secs][CMS: 0K->1163K(174784K), 0.0311840 secs] 6996K->1163K(253440K), [CMS Perm : 3060K->3059K(21248K)], 0.0349020 secs] [Times: user=0.03 sys=0.02, real=0.03 secs] 
複製代碼
  • [GC 代表停頓的類型,有Full則代表發生了Full GC
  • [ParNew 表示垃圾收集的區域與對應的GC收集器 ;其中 [ParNew 代表用的是ParNew收集器; [DefNew 代表使用的是Serial收集器;[PSYoungGen 代表使用的是 Parallel Scavenge收集器;[CMS 這種表示CMS收集器
  • 6996K->1202K(78656K) 表示「GC前該區域已使用容量->GC後該區域已使用容量(該內存區域的總容量)」
  • 0.0036460 secs 表示該內存區域GC所用的時間
  • 6996K->1163K(253440K) (方括號外)的表示"GC前java堆已使用的容量->GC後Java堆使用的容量(Java堆總容量)"
  • [Times: user=0.03 sys=0.02, real=0.03 secs] 分別表示用戶態消耗的CPU時間、內核態的CPU時間和操做從開始到結束所通過的強鍾時間

    牆鍾時間包含各類非運算的等待耗時,例如等待磁盤IO,CPU時間則不包含這些,可是多線程會疊加CPU的時間

相關文章
相關標籤/搜索