天天看點

Java垃圾回收

垃圾收集算法

引用計數

堆中的每個對象都有一個引用計數,當對象被引用時引用計數加1,當對象的引用被重新指派或超出有效區域時引用計數減1,當一個對象被回收後,它所引用的對象的引用計算減1。當一個對象的引用計數變為0時就被回收。

引用計數的優點:

垃圾收集器可以很快地執行,當一個對象的引用數為0時就可以回收這個對象,垃圾收集交織在程式的正常執行過程中,不用長時間中斷程式的正常執行。

引用計數的缺點:

  1. 每次引用計數的增加和減少會帶來額外的開銷
  2. 無法檢測出循環引用

根搜尋算法

垃圾檢測通過建立一個根對象的集合(局部變量、棧桢中的操作數,在本地方法中引用的對象,常量池等)并檢查從這些根對象開始的可觸及性來實作。根對象總是可通路的,如果存在根對象到一個對象的引用路徑,那麼稱這個對象是可觸及的或活動對象,否則是不可觸及的,不可觸及的對象就是垃圾對象。

标記清除

分為标記和清除兩個階段,在标記階段,垃圾收集器跟蹤從根對象的引用,在追蹤的過程中對遇到的對象打一個标記,最終未被标記的對象就是垃圾對象,在清除階段,回收垃圾對象占用的記憶體。可以在對象本身添加跟蹤标記,也可以用一個獨立的位圖來設定标記。

标記清除法是基礎的收集算法,其他算法大多時針對這個算法缺點的改進。

有兩個缺點:

  1. 效率
  2. 存在記憶體碎片

複制算法

将記憶體劃分為大小相等的兩個區域,每次隻使用其中的一個區域,當這個區域的記憶體用完了,就将可觸及的對象直接複制到新的區域并連續存放以消除記憶體碎片,當可觸及對象複制完後,清除舊記憶體區域,修改引用的值。

這種算法的缺點很明顯,可使用記憶體變為了原來的一半,太過浪費。

一般情況下,新生代中的對象大多生命周期很短,也就是說當進行垃圾收集時,大部分對象都是垃圾,隻有一小部分對象會存活下來,是以隻要保留一小部分記憶體儲存存活下來的對象就行了,用不着使用一半的記憶體。在新生代中一般将記憶體劃分為三個部分:一個較大的Eden空間和兩個較小的Survior空間(一樣大小),每次使用Eden和一個Survior的記憶體,進行垃圾收集時将Eden和使用的Survior中的存活的對象複制到另一個Survior空間中,然後清除這兩個空間的記憶體,下次使用Eden和另一個Survior,HotSpot中預設将這三個空間的比例劃分為8:1:1,這樣被浪費掉的空間就隻有總記憶體的1/10了。

這樣的記憶體空間劃分是基于這樣一種假設,即每次垃圾收集時大部分對象都是垃圾,隻有少部分對象存活。如果遇到例外的情況怎麼辦,在某次垃圾收集時存活下來的對象超過了預留的那個Survior空間的總大小,這就需要依賴其他的記憶體進行配置設定擔保了(參考分代收集,前面的描述中也說了這是新生代中的方法)

标記整理

普通的标記清除會在記憶體中留下記憶體碎片,複制算法如果不想浪費掉50%記憶體就需要有記憶體配置設定擔保,一般是記憶體分代,但總有一代是沒有其他代為它擔保的。标記整理算法中标記的過程同标記清理一樣,但整理部分不是直接清除掉垃圾對象,而是将活動對象統一移動一記憶體的一端,然後清除邊界外的記憶體區域,這樣就避免了記憶體碎片。也不會浪費記憶體,不需要其他記憶體進行擔保

分代收集

大多數程式中建立的大部分對象生命周期都很短,而且會有一小部分生命周期長的對象,為了克服複制收集器中每次垃圾收集都要拷貝所有的活動對象的缺點,将記憶體劃分為不同的區域,更多地收集短生命周期所在的記憶體區域,當對象經曆一定次數的垃圾收集存活時,提升它的存在的區域。一般是劃分為新生代和老年代。新生代又劃分為Eden區,From Survior區和To Survior區。

自适應收集器

監聽堆中的情形,并且對應地調用合适的垃圾收集技術。

垃圾收集器

Serial

一個單線程的收集器,在進行垃圾收集時會暫停其他線程的工作,不适合用到Server端的虛拟機,但Client模式的模拟機還是可以用的,因為Client模式下的應用配置設定到的系統記憶體一般不大,垃圾收集可以很快完成。優點就是簡單高效,沒有線程互動開銷,可以獲得最高的單線程收集效率。

ParNew

Seria的多線程版本,可以多個線程收集垃圾,但如果CPU隻有一核且沒有超線程,效果就不一定比Serial好了,如果是多核或有超線程,可以保證效果好于Serial,除Seria之外,這是唯一能與CMS收集器配合的垃圾收集器

Parallel Scavenge

使用複制算法的新生代多線程垃圾收集器,Parallel Scavenge收集器的關注點和其他收集器不同,其他收集器的關注點是盡可能縮短垃圾收集時使用者線程等待的時間,而Parallel Scavenge收集器的目标是達到一個可控制的吞吐量(Throughput),即CPU用于運作使用者代碼的時間與CPU總消耗時間的比值。以縮短使用者線程等待時間的收集器适合用于需要與使用者互動的程式,而以吞吐量為目标的收集器适合用于不需要和使用者太多的互動,以背景運算為目标的任務。

Parallel Scavenge可以通過參數設定每次垃圾收集需要停頓的時間和吞吐量目标,但停頓時間并不是越小越好,這是以犧牲吞吐量和新生代空間為代價的,因為要使垃圾收集停頓時間縮小,隻能進行少量多次收集,或減小需要收集的空間大小。

還有一個-XX:UseAdaptiveSizePolicy參數,指定這個參數後,就不需要手工指定新生代的大小、Eden區和Survior區的比例大小和晉升老年代對象年齡等細節參數了,虛拟機會根據收集到的資訊動态調整這些參數,這稱為自适應政策。

Serial Old

Serial的老年代版本,單線程收集器,使用"标記-整理"算法,主要被Client模式下的虛拟機使用,當被使用在Server模式時主要有兩個用途:

  1. 與Parallel Scavenge配合使用
  2. 作為CMS收集失敗時的備選方案。

Parallel Old

Parallel Scavenge的老年代版本,使用"标記-整理"算法,JDK1.6後提供的,在此之前,如果新生代選擇了Parallel Scavenge,老年代隻能選擇Serial Old,由于Serial Old是單線程的垃圾收集器,可能會影響收集性能。Parallel Old出現後,就可以分别在新生代和老年代選擇Parallel Scavenge和Parallel Old組合了。

CMS(Concurrent Mark Sweep)

以擷取最短回收停頓時間為目标的收集器,使用“标記-清除”算法,整個回收過程分為以下4步:

  • 初始标記(CMS Initial Mark)
  • 并發标記(CMS Current Mark)
  • 重新标記(CMS Remark)
  • 并發清楚(CMS Concurrent Sweep)

初始标記與重新标記階段仍會暫停使用者線程的運作。

初始标記隻是記錄下GC Root能直接關聯到的對象,速度很快。

并發标記就是GC Roots Tracing了,速度較慢,但可以和使用者線程同時運作。

重新标記是修正并發标記時由于使用者線程運作導緻的标記記錄變動,這個階段會使使用者線程停頓,停頓時間比初始标記略長,但仍小于重新标記。

并發清除就是清除垃圾對象了,耗時較長,但可與使用者線程同時工作。

CMS的缺點

  1. 對CPU資源敏感,并發階段和使用者線程同時運作,影響伺服器的響應速度,尤其是CPU核心數少時
  2. 無法處理浮動垃圾,由于并發階段使用者線程同時在運作,可能會在垃圾收集過程中産生新的垃圾,CMS無法處理這部分浮動垃圾,由于在進行垃圾收集時使用者線程同時在運作,需要額外的記憶體空間,是以不能等到記憶體滿時再進行GC,需要預留一部分空間,如果預留的這部分空間不夠GC時使用者線程建立新對象使用,就會使用預備方法,使用Serial Old進行一次Full GC。
  3. CMS基于“标記-清除”算法,進行垃圾回收後會存在記憶體碎片,當申請大的連續記憶體時可能記憶體不足,此時需要進行一次Full GC,可以通過參數指定進行Full GC後或進行多少次Full GC後進行一次記憶體壓縮來整理記憶體碎片。

G1(Garbage First)

基于"标記-整理"算法,避免了記憶體碎片的問題,并可精确地控制垃圾回收時的停頓。

G1收集器可以實作基本不犧牲吞吐量的前提下完成低停頓的記憶體回收,不同于之前的垃圾回收器,G1收集器的回收區域不是整個新生代或老年代,而是将整個Java堆劃分為多個固定大小的區域,并跟蹤這些區域裡的垃圾堆積程度,在背景維護一個優先清單,優先回收垃圾最多的區域。區域的劃分使每次回收時間變短,而優先級的劃分使得每次回收的區域可以回收最多的垃圾,這就使用G1收集器可以在有限的時間内擷取最高的收集效率。

記憶體配置設定與回收政策

對像優先在新生代Eden區配置設定,當Eden區沒有足夠的記憶體時會發生一次Minor GC(新生代GC,Major GC或Full GC是老年代GC)

大對象可以直接在老年代配置設定記憶體,可以通過參數指定一個大小,大于這個大小的對象直接在老年代中配置設定記憶體。

進行Minor GC時,Eden區和一個Survior區中存活的對象會被複制到另一個Survior區,一個對象每在一次Minor GC中存活下來一次後這個對象的年齡就加1,當這個對象的年齡大于一定值(預設15)就會進入老年代。

如果Survior中相同年齡的對象占用的空間大于Survior空間的一半,那麼年齡大于或等于這個年齡的對象會直接進入老年代,而不用等到達到特定年齡

當進行Minor GC時,虛拟機會檢測之前每次晉升到老年代的平均大小是否大于老年代的剩餘空間大小,如果小于,判斷是否開啟了HandlerPromotionFailure允許擔保失敗,如果開啟了就隻進行Minor GC,否則進行Full GC。由于使用的之前Minor GC時的平均大小,如果某一次突然大小變大,導緻老年代剩餘空間不夠,即擔保失敗,會再進行一次Full GC。

finalize

GC時會對活動對象進行标記,沒有被标記的對象就是垃圾對象,但垃圾對象不會直接被清除,垃圾收集器還會判斷是否需要執行對象的finalize方法,如果對象沒有覆寫finalize方法或它的finalize已經被執行過一次,那麼是沒有必要執行的,否則就認為是有必要執行的,當被判斷為有必要執行時,這個對象會被放入一個F-Queue隊列中,由一個背景的低優先級的Finalizer線程執行隊列中的對象的finalize方法,對象可以在這個方法中中複活自己,即重新被其他對象引用,但這個函數隻會被垃圾收集器運作一下,第二次回收這個對象時這個函數不會再被調用。稍後GC會對F-Queue隊列中的對象執行第二次标記。

作者:AngelDevil

出處:www.cnblogs.com/angeldevil

轉載請注明出處!