天天看點

JavaSE_ JVM GC垃圾回收算法一、對象存活判斷二、JVM的垃圾回收過程三、垃圾收集算法四、Minor collections 和 Major collections 五、導緻GC的情況:六、GC運作的三種方式 七、關于finalize方法的問題八、對象引用的類型 二、垃圾回收器五、增量式GC

參考文章:

http://coderbee.net/index.php/jvm/20131031/547

http://blog.chinaunix.net/uid-7374279-id-4489100.html

http://blog.csdn.net/salahg/article/details/5912101

一、對象存活判斷

判斷對象是否存活一般有兩種方式:

   1.引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。此方法簡單,無法解決對象互相循環引用的問題。

  2.可達性分析(Reachability Analysis):從GC Roots開始向下搜尋,搜尋所走過的路徑稱為引用鍊。當一個對象到GC Roots沒有任何引用鍊相連時,則證明此對象是不可用的,即不可達對象。

  Java 中對象的存活分析使用的是可達性分析 !

在Java語言中,GC Roots包括:

  • 虛拟機棧中引用的對象。
  • 方法區中類靜态屬性實體引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI引用的對象。

二、JVM的垃圾回收過程

    首先從GC Roots開始進行可達性分析,判斷哪些是不可達對象。

    對于不可達對象,判斷是否需要執行其finalize方法,如果對象沒有覆寫finalize方法或已經執行過finalize方法則視為不需要執行,進行回收;如果需要,則把對象加入F-Queue隊列。

   對于F-Queue隊列裡的對象,稍後虛拟機會自動建立一個低優先級的線程去觸發其finalize方法,但不會等待這個方法傳回。

    如果在finalize方法的執行過程中,對象重新被引用,那麼進行第二次标記時将被移出F-Queue,在finalize方法執行完成後,對象仍然沒有被引用,則進行回收。

   對于被移出F-Queue的對象,如果它下一次面臨回收時,将不會再執行其finalize方法。即  finalize方法隻執行一次。

三、垃圾收集算法

1、引用計數(reference counting)

    原理:此對象有一個引用,則+1;删除一個引用,則-1。隻用收集計數為0的對象。

    缺點:無法處理循環引用的問題。如:對象A和B分别有字段b、a,令A.b=B和B.a=A,除此之外這2個對象再無任何引用,那實際上這2個對象已經不可能再被通路,但是引用計數算法卻無法回收他們。 

2、複制(copying)

     原理:把記憶體空間劃分為2個相等的區域,每次隻使用一個區域。垃圾回收時,周遊目前使用區域,把正在使用的對象複制到另外一個區域。

    優點:不會出現碎片問題。

    缺點:

     1、暫停整個應用。

     2、需要2倍的記憶體空間。

3、标記-清掃(Mark-and-sweep)---sun前期版本就是用這個技術。

    原理:對于“活”的對象,一定可以追溯到其存活在堆棧、靜态存儲區之中的引用。這個引用鍊條可能會穿過數個對象層次。第一階段:從GC roots開始周遊所有的引用,對有活的對象進行标記。第二階段:對堆進行周遊,把未标記的對象進行清除。這個解決了循環引用的問題。

   缺點:

    1、暫停整個應用;

    2、會産生記憶體碎片。

4、标記-壓縮(Mark-Compact)自适應

    原理:第一階段标記活的對象,第二階段把為标記的對象壓縮到堆的其中一塊,按順序放。

   優點:

      1、避免标記掃描的碎片問題;

      2、避免停止複制的空間問題。

    具體使用什麼方法GC,Java虛拟機會進行監視,如果所有對象都很穩定,垃圾回收器的效率低的話,就切換到“标記-掃描”方式;同樣,Java虛拟機會跟蹤“标記-掃描”的效果,要是堆空間碎片出現很多碎片,就會切換回“停止-複制”模式。這就是自适應的技術。

5、分代(generational collecting)-----J2SE1.2以後使用此算法

     原理:基于對象生命周期分析得出的垃圾回收算法。把對象分為年輕代、年老代、持久代,對不同的生命周期使用不同的算法(2-3方法中的一個即4自适應)進行回收。

6、自适應算法(Adaptive Collector)

    在特定的情況下,一些垃圾收集算法會優于其它算法。基于Adaptive算法的垃圾收集器就是監控目前堆的使用情況,并将選擇适當算法的垃圾收集器。

JavaSE_ JVM GC垃圾回收算法一、對象存活判斷二、JVM的垃圾回收過程三、垃圾收集算法四、Minor collections 和 Major collections 五、導緻GC的情況:六、GC運作的三種方式 七、關于finalize方法的問題八、對象引用的類型 二、垃圾回收器五、增量式GC

        如上圖所示:為Java的各代分布圖

年輕代(young)

    年輕代分三個區。一個Eden區,兩個Survivor區。

    大部分對象在Eden區中生成。當Eden區滿時,還存活的對象将被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象将被複制到另外一個Survivor區,當第二個Survivor區也滿了的時候,從第一個Survivor區複制過來的并且此時還存活的對象,将被複制到tenured generation。

  而且,Survivor區總有一個是空的。young generation的gc稱為minor gc。經過數次minor gc,依舊存活的對象,将被移出young generation,移到tenured generation。

需要注意,Survivor的兩個區是對稱的,沒先後關系,是以同一個區中可能同時存在從Eden複制過來對象,和從前一個 Survivor複制過來的對象,而複制到年老區的隻有從第一個Survivor去過來的對象.

年老代(tenured)

    存放從年輕代(young)複制過來的對象。生命周期較長的對象,歸入到tenured generation。一般是經過多次minor gc,還依舊存活的對象,将移入到tenured generation。(當然,在minor gc中如果存活的對象的超過survivor的容量,放不下的對象會直接移入到tenured generation)。tenured generation的gc稱為major gc,就是通常說的full gc。

    采用compaction算法。由于tenured generaion區域比較大,而且通常對象生命周期都比較長,compaction需要一定時間。是以這部分的gc時間比較長。

   minor gc可能引發full gc。當eden+from space的空間大于tenured generation區的剩餘空間時,會引發full gc。這是悲觀算法,要確定eden+from space的對象如果都存活,必須有足夠的tenured generation空間存放這些對象。

持久代(perm)

    用于存放靜态檔案,如Java類、方法等。持久代對垃圾回收沒有顯著的影響,但是有些應用可能動态生成或者調用一些class。持久代大小通過-XX:MaxPermSize=N進行設定

   該區域比較穩定,主要用于存放classloader資訊,比如類資訊和method資訊。

   對于spring hibernate這些需要動态類型支援的架構,這個區域需要足夠的空間。(這部分空間應該存在于方法區而不是heap中)。

四、Minor collections 和 Major collections 

   Minor collection當young space被占滿時執行。它比major collections快,因為minor collection僅僅檢查major collection相應的一個子集對象。minor collection比major collection發生的頻率高。

   Major collection當tenured space被占滿時執行。他會清理tenured和young。

   Thinking in java給java gc取了一個羅嗦的稱呼:“自适應、分代的、停止-複制、标記-掃描”式的垃圾回收器。

五、導緻GC的情況:

1、tenured被寫滿

2、perm被寫滿

3、System.gc()的顯式調用。

4、上一次GC之後heap的各域配置設定政策動态變化。

六、GC運作的三種方式 

  在java5和java6中有4種 垃圾回收的算法,有一種算法将不再支援,剩餘的三種垃圾回收算法是:serial, throughput and concurrent low pause。 

   Stop the world(停止所有程式的方式):在這種方式運作的GC,在GC完成前,JVM中的所有程式都不允許運作。Serial collector此時做minor和major收集。Throughput collector此時做major collector。

   Incremental(增量運作方式):目前沒要Java GC算法支援這種運作方式。GC以這種方式運作時,GC允許程式做一小段時間的工作,然後做垃圾回收工作。

   Concurrent(并行運作):Throughput collector此時做minor collect,Concurrent low pause collector此時做minor和major收集。在這種運作方式下,GC和程式并行的運作,是以程式僅僅被短暫的暫停。

七、關于finalize方法的問題

   finalize方法使得GC過程做了更多的事情,增加的GC的負擔。如果某個對象的finalize方法運作時間過長,它會使得其他對象的finalize方法被延遲執行。

  finalize方法中如果建立了strong reference引用了其他對象,這會阻止此對象被GC。

  finalize方法有可能以不可确定的順序執行(也就是說要在安全性要求嚴格的場景中盡量避免使用finalize方法)。

  不確定finalize方法會被及時調用,也許程式都退出了,但是finalize方法還沒被調用。

八、對象引用的類型 

  •    Reference(or named Strong Reference)( 強引用):

     普通類型的引用。

  •    SoftReference( 軟引用):

      被這種引用指向的對象,如果此對象沒要再被其他Strong Reference引用的話,可能在任何時候被GC。雖然是可能在任何時候被GC,但是通常是在可用記憶體數比較低的時候,并且在程式抛出OutOfMemoryError之前才發生對此對象的GC。SoftReference通常被用作實作Cache的對象引用,如果這個對象被GC了,那麼他可以在任何時候再重新被建立。另外,根據JDK文檔中介紹,實際JVM的實作是鼓勵不回收最近建立和最近使用的對象。SoftReference 類的一個典型用途就是用于記憶體敏感的高速緩存。

  •    WeakReference(弱引用):

      如果一個被WeakReference引用的對象,當沒要任何SoftReference和StrongReference引用時,立即會被GC。和SoftReference的差別是:WeakReference對象是被eagerly collected,即一旦沒要任何SoftReference和StrongReference引用,立即被清楚;而隻被SoftReference引用的對象,不回立即被清楚,隻有當記憶體不夠,即将發生OutOfMemoryError時才被清除,而且是先清除不常用的。SoftReference适合實作Cache用。WeakReference 類的一個典型用途就是規範化映射( canonicalized mapping )

  •  PhantomReference(虛引用):

    當沒有StrongReference,SoftReference和WeakReference引用時,随時可被GC。通常和ReferenceQueue聯合使用,管理和清除與被引用對象(沒有finalize方法)相關的本地資源。

二、垃圾回收器

目前的收集器主要有三種:串行收集器、并行收集器、并發收集器。

1. 串行收集器

   使用單線程處理所有垃圾回收工作,因為無需多線程互動,是以效率比較高。但是,也無法使用多處理器的優勢,是以此收集器适合單處理器機器。當然,此收集器也可以用在小資料量(100M左右)情況下的多處理器機器上。可以使用-XX:+UseSerialGC打開。

2. 并行收集器 

  1) 對年輕代進行并行垃圾回收,是以可以減少垃圾回收時間。一般在多線程多處理器機器上使用。使用-XX:+UseParallelGC.打開。并行收集器在J2SE5.0第六6更新上引入,在Java SE6.0中進行了增強--可以堆年老代進行并行收集。如果年老代不使用并發收集的話,是使用單線程進行垃圾回收,是以會制約擴充能力。使用-XX:+UseParallelOldGC打開。

  2) 使用-XX:ParallelGCThreads=<N>設定并行垃圾回收的線程數。此值可以設定與機器處理器數量相等。 

  3)此收集器可以進行如下配置: 

   * 最大垃圾回收暫停:指定垃圾回收時的最長暫停時間,通過-XX:MaxGCPauseMillis=<N>指定。<N>為毫秒.如果指定了此值的話,堆大小和垃圾回收相關參數會進行調整以達到指定值。設定此值可能會減少應用的吞吐量。 

  * 吞吐量:吞吐量為垃圾回收時間與非垃圾回收時間的比值,通過-XX:GCTimeRatio=<N>來設定,公式為1/(1+N)。例如,-XX:GCTimeRatio=19時,表示5%的時間用于垃圾回收。預設情況為99,即1%的時間用于垃圾回收。

3. 并發收集器 

   可以保證大部分工作都并發進行(應用不停止),垃圾回收隻暫停很少的時間,此收集器适合對響應時間要求比較高的中、大規模應用。使用-XX:+UseConcMarkSweepGC打開。 

   1)  并發收集器主要減少年老代的暫停時間,他在應用不停止的情況下使用獨立的垃圾回收線程,跟蹤可達對象。在每個年老代垃圾回收周期中,在收集初期并發收集器會對整個應用進行簡短的暫停,在收集中還會再暫停一次。第二次暫停會比第一次稍長,在此過程中多個線程同時進行垃圾回收工作。 

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

  3) 在隻有一個處理器的主機上使用并發收集器,設定為incremental mode模式也可獲得較短的停頓時間。 

  4) 浮動垃圾:由于在應用運作的同時進行垃圾回收,是以有些垃圾可能在垃圾回收進行完成時産生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收周期時才能回收掉。是以,并發收集器一般需要20%的預留白間用于這些浮動垃圾。 

  5) Concurrent Mode Failure:并發收集器在應用運作時進行收集,是以需要保證堆在垃圾回收的這段時間有足夠的空間供程式使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下将會發生“并發模式失敗”,此時整個應用将會暫停,進行垃圾回收。 

 6) 啟動并發收集器:因為并發收集在應用運作時進行收集,是以必須保證收集完成之前有足夠的記憶體空間供程式使用,否則會出現“Concurrent Mode Failure”。通過設定-XX:CMSInitiatingOccupancyFraction=<N>指定還有多少剩餘堆時開始執行并發收集

4. 小結 

* 串行處理器: 

--适用情況:資料量比較小(100M左右);單處理器下并且對響應時間無要求的應用。 

--缺點:隻能用于小型應用 

* 并行處理器: 

--适用情況:“對吞吐量有高要求”,多CPU、對應用響應時間無要求的中、大型應用。舉例:背景處理、科學計算。 

--缺點:應用響應時間可能較長 

* 并發處理器: 

--适用情況:“對響應時間有高要求”,多CPU、對應用響應時間有較高要求的中、大型應用。舉例:Web伺服器/應用伺服器、電信交換、內建開發環境。

五、增量式GC

    增量式GC(Incremental GC),是GC在JVM中通常是由一個或一組程序來實作的,它本身也和使用者程式一樣占用heap空間,運作時也占用CPU。

   當GC程序運作時,應用程式停止運作。是以,當GC運作時間較長時,使用者能夠感到Java程式的停頓,另外一方面,如果GC運作時間太短,則可能對象回收率太低,這意味着還有很多應該回收的對象沒有被回收,仍然占用大量記憶體。是以,在設計GC的時候,就必須在停頓時間和回收率之間進行權衡。一個好的GC實作允許使用者定義自己所需要的設定,例如有些記憶體有限的裝置,對記憶體的使用量非常敏感,希望GC能夠準确的回收記憶體,它并不在意程式速度的快慢。另外一些實時網絡遊戲,就不能夠允許程式有長時間的中斷。

增量式GC就是通過一定的回收算法,把一個長時間的中斷,劃分為很多個小的中斷,通過這種方式減少GC對使用者程式的影響。雖然,增量式GC在整體性能上可能不如普通GC的效率高,但是它能夠減少程式的最長停頓時間。

   Sun JDK提供的HotSpot JVM就能支援增量式GC。HotSpot JVM預設GC方式為不使用增量GC,為了啟動增量GC,我們必須在運作Java程式時增加-Xincgc的參數。

    HotSpot JVM增量式GC的實作是采用Train GC算法,它的基本想法就是:将堆中的所有對象按照建立和使用情況進行分組(分層),将使用頻繁高和具有相關性的對象放在一隊中,随着程式的運作,不斷對組進行調整。當GC運作時,它總是先回收最老的(最近很少通路的)的對象,如果整組都為可回收對象,GC将整組回收。這樣,每次GC運作隻回收一定比例的不可達對象,保證程式的順暢運作。

繼續閱讀