JVM垃圾回收算法(最全)

JVM垃圾回收算法(最全)前端

 

下面是JVM虛擬機運行時的內存模型:java

 

1.方法區 Perm(永久代、非堆)web

2.虛擬機棧算法

3.本地方法棧 (Native方法)bootstrap

4.堆服務器

5.程序計數器多線程

 

1 首先的問題是:jvm如何知道那些對象須要回收 ?

目前兩種標識算法、三種回收算法、兩種清除算法、三種收集器併發

  • 引用計數法

每一個對象上都有一個引用計數,對象每被引用一次,引用計數器就+1,對象引用被釋放,引用計數器-1,直到對象的引用計數爲0,對象就標識能夠回收jvm

這個能夠用數據算法中的圖形表示,對象A-對象B-對象C 都有引用,因此不會被回收,對象B因爲沒有被引用,沒有路徑能夠達到對象B,對象B的引用計數就就是0,對象B就會被回收。性能

 

 2

可是這個算法有明顯的缺陷,對於循環引用的狀況下,循環引用的對象就不會被回收。例以下圖:對象A,對象B 循環引用,沒有其餘的對象引用A和B,則A和B 都不會被回收。

 3

  • root搜索算法

這種算法目前定義了幾個root,也就是這幾個對象是jvm虛擬機不會被回收的對象,因此這些對象引用的對象都是在使用中的對象,這些對象未使用的對象就是即將要被回收的對象。簡單就是說:若是對象可以達到root,就不會被回收,若是對象不可以達到root,就會被回收。

以下圖:對象D訪問不到根對象,因此就會被回收

4

如下對象會被認爲是root對象:

  • 被啓動類(bootstrap加載器)加載的類和建立的對象
  • jvm運行時方法區類靜態變量(static)引用的對象
  • jvm運行時方法去常量池引用的對象
  • jvm當前運行線程中的虛擬機棧變量表引用的對象
  • 本地方法棧中(jni)引用的對象

因爲這種算法即便存在互相引用的對象,但若是這兩個對象沒法訪問到根對象,仍是會被回收。以下圖:對象C和對象D互相引用,可是因爲沒法訪問根,因此會被回收。

5

jvm在肯定是否回收的對象的時候採用的是root搜索算法來實現。

在root搜索算法的裏面,咱們說的引用這裏都指定的是強引用關係。所謂強引用關係,就是經過用new 方式建立的對象,而且顯示關聯的對象

[java]  view plain  copy
 
  1. <span style="font-family:Microsoft YaHei;font-size:14px;"><span style="font-family:Microsoft YaHei;font-size:14px;">Object obj = new Object();</span></span>  

以上就是表明的是強引用關係,變量obj 強引用了 Object的一個對象。

java裏面有四種應用關係,從強到弱分別爲:

Strong Reference(強引用) –>Weak Reference (弱引用) -> Soft Reference(軟引用) – > Phantom Reference(引用)

 

Strong Reference : 只有在引用對象root不可達的狀況下才會標識爲可回收,垃圾回收纔可能進行回收

Weak Reference :即便在root算法中 其引用的對象root可達到,可是若是jvm堆內存 不夠的時候,仍是會被回收。

Soft Reference : 不管其引用的對象是否root可達,在響應內存須要時,由垃圾回收判斷是否須要回收。

Phantom Reference :在回收器肯定其指示對象可另外回收以後,被加入垃圾回收隊列.

 

 

  • 標記-清除

標記清除的算法最簡單,主要是標記出來須要回收的對象,而後而後把這些對象在內存的信息清除。如何標記須要回收的對象,在上一篇文章裏面已經有說明。

2_1

 

  • 標記-清除-壓縮

這個算法是在標記-清除的算法之上進行一下壓縮空間,從新移動對象的過程。由於標記清除算法會致使不少的留下來的內存空間碎片,隨着碎片的增多,嚴重影響內存讀寫的性能,因此在標記-清除以後,會對內存的碎片進行整理。最簡單的整理就是把對象壓縮到一邊,留出另外一邊的空間。因爲壓縮空間須要必定的時間,會影響垃圾收集的時間。

2_2

 

  • 標記-清除-複製

這個算法是吧內存分配爲兩個空間,一個空間(A)用來負責裝載正常的對象信息,,另一個內存空間(B)是垃圾回收用的。每次把空間A中存活的對象所有複製到空間B裏面,在一次性的把空間A刪除。這個算法在效率上比標記-清除-壓縮高,可是須要兩塊空間,對內存要求比較大,內存的利用率比較低。適用於短生存期的對象,持續複製長生存期的對象則致使效率下降

2_3 

因爲如今的處理器都是多核的,處理器的性能獲得了極大的提高,因此在此基礎上有產生了幾種垃圾收集算法。主要包括兩種算法

  • 並行標記清除

所謂並行,就是原來垃圾回收只是一個線程進行。如今建立多個垃圾回收線程。並行的進行標記和清除。好比把須要標記的對象平均分配到多個線程以後,當標記完成以後,多個線程進行清除。

 

  • 併發標記清除

所謂併發,就是應用程序和垃圾回收能夠同時執行。在標記清除算法中,在標記對象和清除對象,以及壓縮對象的狀況下是須要暫停應用的。那麼並行標記清除壓縮算法則是在標記清除壓縮算法的基礎上,把標記清除壓縮算法分爲如下幾個過程

初始標記->併發標記->從新標記->併發清除->重置

 

以上幾種算法是垃圾回收的基本算法,jvm垃圾回收就是在以上幾種算法爲基礎的,在以上幾種算法的基礎上,java垃圾回收器能夠分爲如下幾種:

  • 串行收集器

用單線程處理全部垃圾回收工做,由於無需多線程交互,因此效率比較高。可是,也沒法使用多處理器的優點,因此此收集器適合單處理器機器

單線程收集器。在目前多核服務器端運行的狀況下,效率比較低。比較適合堆內存小的狀況下使用。

2_5

  • 並行收集器

用多線程處理全部垃圾回收工做,利用多核處理器的優點。可是若是線程數量過多,致使線程之間頻繁調度,也會影響性能。一半並行收集的線程是處理器的個數。

「對吞吐量有高要求」,多CPU、對應用響應時間無要求的中、大型應用。舉例:後臺處理、科學計算。

2_6

  • 併發收集器

併發收集器主要減小年老代的暫停時間,他在應用不中止的狀況下使用獨立的垃圾回收線程,跟蹤可達對象。在每一個年老代垃圾回收週期中,在收集初期併發收集器 會對整個應用進行簡短的暫停(初始標記的過程),在收集中還會再暫停一次。第二次暫停會比第一次稍長(從新標記的過程),在此過程當中多個線程同時進行垃圾回收工做。

併發收集器使用處理器換來短暫的停頓時間。在一個N個處理器的系統上,併發收集部分使用K/N個可用處理器進行回收,通常狀況下1<=K<=N/4。

在只有一個處理器的主機上使用併發收集器,設置爲incremental mode模式也可得到較短的停頓時間。

浮動垃圾:因爲在應用運行的同時進行垃圾回收,因此有些垃圾可能在垃圾回收進行完成時產生,這樣就形成了「Floating Garbage」,這些垃圾須要在下次垃圾回收週期時才能回收掉。因此,併發收集器通常須要20%的預留空間用於這些浮動垃圾。

Concurrent Mode Failure:併發收集器在應用運行時進行收集,因此須要保證堆在垃圾回收的這段時間有足夠的空間供程序使用,不然,垃圾回收還未完成,堆空間先滿了。這種狀況下將會發生「併發模式失敗」,此時整個應用將會暫停,進行垃圾回收。

併發收集器,在垃圾回收的時候採用併發標記清除算法的收集器

對響應時間要求高的,多CPU,大型應用。好比頁面請求/web服務器。前端業務系統用的比較多。

2_7

 

串行處理器:

--適用狀況:數據量比較小(100M左右);單處理器下而且對響應時間無要求的應用。

--缺點:只能用於小型應用

並行處理器:

--適用狀況:「對吞吐量有高要求」,多CPU、對應用響應時間無要求的中、大型應用。舉例:後臺處理、科學計算。

--缺點:垃圾收集過程當中應用響應時間可能加長

併發處理器:

--適用狀況:「對響應時間有高要求」,多CPU、對應用響應時間有較高要求的中、大型應用。舉例:Web服務器/應用服務器、電信交換、集成開發環境。

JDK5.0適用的分代垃圾回收算法

       分代的垃圾回收策略,是基於這樣一個事實:不一樣的對象的生命週期是不同的。所以,不一樣生命週期的對象能夠採起不一樣的收集方式,以便提升回收效率。

       在Java程序運行的過程當中,會產生大量的對象,其中有些對象是與業務信息相關,好比Http請求中的Session對象、線程、Socket鏈接,這類對象跟業務直接掛鉤,所以生命週期比較長。可是還有一些對象,主要是程序運行過程當中生成的臨時變量,這些對象生命週期會比較短,好比:String對象,因爲其不變類的特性,系統會產生大量的這些對象,有些對象甚至只用一次便可回收。

       試想,在不進行對象存活時間區分的狀況下,每次垃圾回收都是對整個堆空間進行回收,花費時間相對會長,同時,由於每次回收都須要遍歷全部存活對象,但實際上,對於生命週期長的對象而言,這種遍歷是沒有效果的,由於可能進行了不少次遍歷,可是他們依舊存在。所以,分代垃圾回收採用分治的思想,進行代的劃分,把不一樣生命週期的對象放在不一樣代上,不一樣代上採用最適合它的垃圾回收方式進行回收。

如何分代

如圖所示:

       虛擬機中的共劃分爲三個代:年輕代(Young Generation)、年老點(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。

年輕代:

      全部新生成的對象首先都是放在年輕代的。年輕代的目標就是儘量快速的收集掉那些生命週期短的對象。年輕代分三個區。一個Eden區,兩個Survivor區(通常而言)。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被複制到另一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的而且此時還存活的對象,將被複制「年老區(Tenured)」。須要注意,Survivor的兩個區是對稱的,沒前後關係,因此同一個區中可能同時存在從Eden複製過來 對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor去過來的對象。並且,Survivor區總有一個是空的。同時,根據程序須要,Survivor區是能夠配置爲多個的(多於兩個),這樣能夠增長對象在年輕代中的存在時間,減小被放到年老代的可能。

年老代:

      在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。所以,能夠認爲年老代中存放的都是一些生命週期較長的對象。

持久代:

      用於存放靜態文件,現在Java類、方法等。持久代對垃圾回收沒有顯著影響,可是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候須要設置一個比較大的持久代空間來存放這些運行過程當中新增的類。持久代大小經過-XX:MaxPermSize=&lt;N>進行設置。

什麼狀況下觸發垃圾回收

因爲對象進行了分代處理,所以垃圾回收區域、時間也不同。GC有兩種類型:Scavenge GC和Full GC。

 

(1)新生代上的GC實現
 
Serial:單線程的收集器,只使用一個線程進行收集,而且收集時會暫停其餘全部
工做線程(Stop the world)。它是Client模式下的默認新生代收集器。
 
ParNew:Serial收集器的多線程版本。在單CPU甚至兩個CPU的環境下,因爲線程
交互的開銷,沒法保證性能超越Serial收集器。
 
Parallel Scavenge:也是多線程收集器,與ParNew的區別是,它是 吞吐量優先
收集器。吞吐量=運行用戶代碼時間/(運行用戶代碼+垃圾收集時間)。另外一點區別
是配置-XX:+UseAdaptiveSizePolicy後,虛擬機會自動調整Eden/Survivor等參數來
提供用戶所需的吞吐量。咱們須要配置的就是內存大小-Xmx和吞吐量GCTimeRatio。
 
(2)老年代上的GC實現
 
Serial Old:Serial收集器的老年代版本。
 
Parallel Old:Parallel Scavenge的老年代版本。此前,若是新生代採用PS GC的話,
老年代只有Serial Old能與之配合。如今有了Parallel Old與之配合,能夠在注重吞吐量
及CPU資源敏感的場合使用了。
 
CMS:採用的是 標記-清除而非標記-整理,是一款併發低停頓的收集器。可是因爲
採用標記-清除,內存碎片問題不可避免。可使用-XX:CMSFullGCsBeforeCompaction
設置執行幾回CMS回收後,跟着來一次內存碎片整理。

 

 

GC類型 
GC有兩種類型:Scavenge GC和Full GC。 

1. Scavenge GC 

通常狀況下,當新對象生成,而且在Eden申請空間失敗時,就好觸發Scavenge GC,堆Eden區域進行GC,清除非存活對象,而且把尚且存活的對象移動到Survivor區。而後整理Survivor的兩個區。 
2. Full GC 
對整個堆進行整理,包括Young、Tenured和Perm。Full GC比Scavenge GC要慢,所以應該儘量減小Full GC。有以下緣由可能致使Full GC: 
* Tenured被寫滿 
* Perm域被寫滿 
* System.gc()被顯示調用 
* 上一次GC以後Heap的各域分配策略動態變化 

 

 

分代垃圾回收流程 

分代垃圾回收流程 

分代垃圾回收流程 

分代垃圾回收流程

相關文章
相關標籤/搜索