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隊列中的對象執行第二次標記。

相關文章
相關標籤/搜索