Android GC,JVM MinorGC/ MajorGC/ FullGC,GC目的

desaco發表於2016-05-20

1.GC回收機制熟悉麼,分代演算法知道麼?
2.瞭解 Java 虛擬機器的垃圾回收演算法?

    從年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC。 Major GC 是清理永久代。Full GC 是清理整個堆空間—包括年輕代和永久代。
> Android GC
Android GC 原理探究- http://geek.csdn.net/news/detail/193654

> Java中的GC是什麼? 為什麼要有GC?
GC目的:回收堆記憶體中不再使用的物件,釋放資源
回收時間:當物件永久地失去引用後,系統會在合適的時候回收它所佔的記憶體。

  GC System.gc()或Runtime.getRuntime().gc(),
  忘記或者錯誤的記憶體回收會導致程式或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測物件是否超過作用域從而達到自動回收記憶體的目的。垃圾回收可以有效的防止記憶體洩露,有效的使用可以使用的記憶體。
 採用“分代式垃圾收集”。這種方法會跟Java物件的生命週期將堆記憶體劃分為不同的區域,在垃圾收集過程中,可能會將物件移動到不同區域: 
 - 伊甸園(Eden):這是物件最初誕生的區域,並且對大多數物件來說,這裡是它們唯一存在過的區域。 
 - 倖存者樂園(Survivor):從伊甸園倖存下來的物件會被挪到這裡。 
 - 終身頤養園(Tenured):這是足夠老的倖存物件的歸宿。年輕代收集(Minor-GC)過程是不會觸及這個地方的。當年輕代收集不能把物件放進終身頤養園時,就會觸發一次完全收集(Major-GC),這裡可能還會牽扯到壓縮,以便為大物件騰出足夠的空間。

  在C/C++中,釋放無用變數記憶體空間的事情要由程式設計師自己來解決。
  Java有了GC,就不需要程式設計師去人工釋放記憶體空間。當Java虛擬機器發覺記憶體資源緊張的時候,就會自動地去清理無用變數所佔用的記憶體空間。當然,如果需要,程式設計師可以在Java程式中顯式地使用System.gc()來強制進行一次立即的記憶體清理。 因為顯式宣告是做堆記憶體全掃描,也就是 Full GC,是需要停止所有的活動的(Stop The World Collection),你的應用能承受這個嗎?而其顯示呼叫System.gc()只是給虛擬機器一個建議,不一 定會執行,因為System.gc()在一個優先順序很低的執行緒中執行。
  java的GC功能可以自動監測物件是否超過作用域從而達到自動回收記憶體的目的,java語言沒有提供釋放已分配記憶體的俄顯示操作方法。在應用程式中,只要某物件變得不可達,也就是沒有根(root)引用該物件,這個物件就會成為垃圾回收器的目標。

GC的基本原理:
 1.對於程式設計師來說,用new關鍵字即在堆中分配了記憶體,我們稱之為“可達”。對於GC來說,只要所有被引用的物件為null時,我們稱之為“不可達”,就將進行記憶體的回收。
 2.當一個物件被建立時,GC開始監控這個物件的大小、記憶體地址及使用情況。GC採用有向圖的方式記錄和管理堆(heap)中的所有物件,通過這種方式可以明確哪些物件是可達的,哪些不是。當確定為不可達時,則對其進行回收。
 3.保證GC在不同平臺的實現問題,java規範對其很多行為沒有進行嚴格的規定。對於採用什麼演算法,什麼時候進行回收等。

-- GC收集器有哪些?

1.serial收集器
單執行緒,工作時必須暫停其他工作執行緒。多用於client機器上,使用複製演算法
2.ParNew收集器
serial收集器的多執行緒版本,server模式下虛擬機器首選的新生代收集器。複製演算法
3.Parallel Scavenge收集器
複製演算法,可控制吞吐量的收集器。吞吐量即有效執行時間。
4.Serial Old收集器
serial的老年代版本,使用整理演算法。
5.Parallel Old收集器
第三種收集器的老年代版本,多執行緒,標記整理
6.CMS收集器
目標是最短回收停頓時間。7.G1收集器,基本思想是化整為零,將堆分為多個Region,優先收集回收價值最大的Region。

> Java GC,GC 演算法,GC機制
記憶體分配過程為(來源於《成為JavaGC專家part I》-http://www.importnew.com/1993.html
成為JavaGC專家Part II — 如何監控Java垃圾回收機制-http://www.importnew.com/2057.html
JDK5.0垃圾收集優化之 - http://calvin.iteye.com/blog/91905
三大Java 虛擬機器垃圾回收機制的比較(HotSpot, JRockit, IBM JVM)- https://blog.csdn.net/ZYC88888/article/details/70918799
垃圾回收器,如何判斷物件是否存活- http://blog.csdn.net/TyroneRenekton/article/details/59114835?ref=myread
JVM初探- 記憶體分配、GC原理與垃圾收集器-- http://blog.csdn.net/zjf280441589/article/details/53946312
深入理解JVM之一:Java記憶體區域: https://yq.aliyun.com/articles/14408#

-- 在Java中,它的記憶體管理包括兩方面:記憶體分配(建立Java物件的時候)和記憶體回收。瞭解JVM,才能寫出更高效,充分利用有限的記憶體的程式。
  Java垃圾回收主要做的是兩件事:1)記憶體回收; 2)碎片整理。

  記憶體管理小技巧:
  1)儘量使用直接量,eg:String javaStr = "小學徒的成長曆程";
  2)使用StringBuilder和StringBuffer進行字串連線等操作;
  3)儘早釋放無用物件;
  4)儘量少使用靜態變數;
  5)快取常用的物件:可以使用開源的開源快取實現,eg:OSCache,Ehcache;
  6)儘量不使用finalize()方法;
  7)在必要的時候可以考慮使用軟引用SoftReference。

-- GC回收機制演算法:分代複製垃圾回收和標記垃圾回收,增量垃圾回收。

   學習Java GC機制,可以幫助我們在日常工作中排查各種記憶體溢位或洩露問題,解決效能瓶頸,達到更高的併發量,寫出更高效的程式。我們將從4個方面學習Java GC機制:

1,記憶體是如何分配的;

2,如何保證記憶體不被錯誤回收(即:哪些記憶體需要回收);

3,在什麼情況下執行GC以及執行GC的方式;

4,如何監控和優化GC機制。

--  瞭解Java GC機制,必須先清楚在JVM中記憶體區域的劃分。
在Java執行時的資料區裡,JVM管理的記憶體區域分為幾個模組:
  1,程式計數器(Program Counter Register):程式計數器是一個比較小的記憶體區域,用於指示當前執行緒所執行的位元組碼執行到了第幾行,可以理解為是當前執行緒的行號指示器。位元組碼直譯器在工作時,會通過改變這個計數器的值來取下一條語句指令。
 每個程式計數器只用來記錄一個執行緒的行號,所以它是執行緒私有(一個執行緒就有一個程式計數器)的。
   如果程式執行的是一個Java方法,則計數器記錄的是正在執行的虛擬機器位元組碼指令地址;如果正在執行的是一個本地(native,由C語言編寫 完成)方法,則計數器的值為Undefined,由於程式計數器只是記錄當前指令地址,所以不存在記憶體溢位的情況,因此,程式計數器也是所有JVM記憶體區 域中唯一一個沒有定義OutOfMemoryError的區域。

  2,虛擬機器棧(JVM Stack):一個執行緒的每個方法在執行的同時,都會建立一個棧幀(Statck Frame),棧幀中儲存的有區域性變數表、操作站、動態連結、方法出口等,當方法被呼叫時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。
   區域性變數表中儲存著方法的相關區域性變數,包括各種基本資料型別,物件的引用,返回地址等。在區域性變數表中,只有long和double型別會佔用2個區域性變數空間(Slot,對於32位機器,一個Slot就是32個bit),其它都是1個Slot。需要注意的是,區域性變數表是在編譯時就已經確定 好的,方法執行所需要分配的空間在棧幀中是完全確定的,在方法的生命週期內都不會改變。
   虛擬機器棧中定義了兩種異常,如果執行緒呼叫的棧深度大於虛擬機器允許的最大深度,則丟擲StatckOverFlowError(棧溢位);不過多 數Java虛擬機器都允許動態擴充套件虛擬機器棧的大小(有少部分是固定長度的),所以執行緒可以一直申請棧,知道記憶體不足,此時,會丟擲 OutOfMemoryError(記憶體溢位)。
   每個執行緒對應著一個虛擬機器棧,因此虛擬機器棧也是執行緒私有的。

  3,本地方法棧(Native Method Statck):本地方法棧在作用,執行機制,異常型別等方面都與虛擬機器棧相同,唯一的區別是:虛擬機器棧是執行Java方法的,而本地方法棧是用來執行native方法的,在很多虛擬機器中(如Sun的JDK預設的HotSpot虛擬機器),會將本地方法棧與虛擬機器棧放在一起使用。
  本地方法棧也是執行緒私有的。

  4,堆區(Heap):堆區是理解Java GC機制最重要的區域,沒有之一。在JVM所管理的記憶體中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要記憶體區域,堆區由所有執行緒共享,在虛擬機器啟動時建立。堆區的存在是為了儲存物件例項,原則上講,所有的物件都在堆區上分配記憶體(不過現代技術裡,也不是這麼絕對的,也有棧上直接分配的)。
  一般的,根據Java虛擬機器規範規定,堆記憶體需要在邏輯上是連續的(在物理上不需要),在實現時,可以是固定大小的,也可以是可擴充套件的,目前主流的虛擬機器堆區都是可擴充套件的。如果在執行垃圾回收之後,仍沒有足夠的記憶體分配,也不能再擴充套件,將會丟擲OutOfMemoryError:Java heap space異常。
    “Java記憶體分配機制”。

  5,方法區(Method Area):在Java虛擬機器規範中,將方法區作為堆的一個邏輯部分來對待,但事實上,方法區並不是堆(Non-Heap);另外,不少人的部落格中,將Java GC的分代收集機制分為3個代:青年代,老年代,永久代,這些作者將方法區定義為“永久代”,這是因為,對於之前的HotSpot Java虛擬機器的實現方式中,將分代收集的思想擴充套件到了方法區,並將方法區設計成了永久代。不過,除HotSpot之外的多數虛擬機器,並不將方法區當做永久代,HotSpot本身,也計劃取消永久代。本文中,由於筆者主要使用Oracle JDK6.0,因此仍將使用永久代一詞。
  方法區是各個執行緒共享的區域,用於儲存已經被虛擬機器載入的類資訊(即載入類時需要載入的資訊,包括版本、field、方法、介面等資訊)、final常量、靜態變數、編譯器即時編譯的程式碼等。
  方法區在物理上也不需要是連續的,可以選擇固定大小或可擴充套件大小,並且方法區比堆還多了一個限制:可以選擇是否執行垃圾收集。一般的,方法區上 執行的垃圾收集是很少的,這也是方法區被稱為永久代的原因之一(HotSpot),但這也不代表著在方法區上完全沒有垃圾收集,其上的垃圾收集主要是針對常量池的記憶體回收和對已載入類的解除安裝。
  在方法區上進行垃圾收集,條件苛刻而且相當困難,效果也不令人滿意,所以一般不做太多考慮,可以留作以後進一步深入研究時使用。
  在方法區上定義了OutOfMemoryError:PermGen space異常,在記憶體不足時丟擲。
  執行時常量池(Runtime Constant Pool)是方法區的一部分,用於儲存編譯期就生成的字面常量、符號引用、翻譯出來的直接引用(符號引用就是編碼是用字串表示某個變數、介面的位置,直接引用就是根據符號引用翻譯出來的地址,將在類連結階段完成翻譯);執行時常量池除了儲存編譯期常量外,也可以儲存在執行時間產生的常量(比如String類的intern()方法,作用是String維護了一個常量池,如果呼叫的字元“abc”已經在常量池中,則返回池中的字串地址,否則,新建一個常量加入池中,並返回地址)。

  6,直接記憶體(Direct Memory):直接記憶體並不是JVM管理的記憶體,可以這樣理解,直接記憶體,就是 JVM以外的機器記憶體,比如,你有4G的記憶體,JVM佔用了1G,則其餘的3G就是直接記憶體,JDK中有一種基於通道(Channel)和緩衝區 (Buffer)的記憶體分配方式,將由C語言實現的native函式庫分配在直接記憶體中,用儲存在JVM堆中的DirectByteBuffer來引用。 由於直接記憶體收到本機器記憶體的限制,所以也可能出現OutOfMemoryError的異常。

-- Java物件的訪問方式。一般來說,一個Java的引用訪問涉及到3個記憶體區域:JVM棧,堆,方法區。
以最簡單的本地變數引用:Object obj = new Object()為例:
  Object obj表示一個本地引用,儲存在JVM棧的本地變數表中,表示一個reference型別資料;
  new Object()作為例項物件資料儲存在堆中;
  堆中還記錄了Object類的型別資訊(介面、方法、field、物件型別等)的地址,這些地址所執行的資料儲存在方法區中;
在Java虛擬機器規範中,對於通過reference型別引用訪問具體物件的方式並未做規定.

--  Java GC機制:

JVM 內建的通用垃圾回收原則。堆記憶體劃分為 Eden、Survivor(年輕代) , Tenured/Old (老年代)空間:
1.Minor GC
    從年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC。但是,當發生Minor GC事件的時候,有一些有趣的地方需要注意到:
    當 JVM 無法為一個新的物件分配空間時會觸發 Minor GC,比如當 Eden 區滿了。所以分配率越高,越頻繁執行 Minor GC。記憶體池被填滿的時候,其中的內容全部會被複制,指標會從0開始跟蹤空閒記憶體。Eden 和 Survivor 區進行了標記和複製操作,取代了經典的標記、掃描、壓縮、清理操作。所以 Eden 和 Survivor 區不存在記憶體碎片。寫指標總是停留在所使用記憶體池的頂部。執行 Minor GC 操作時,不會影響到永久代。從永久代到年輕代的引用被當成 GC roots,從年輕代到永久代的引用在標記階段被直接忽略掉。
   質疑常規的認知,所有的 Minor GC 都會觸發“全世界的暫停(stop-the-world)”,停止應用程式的執行緒。對於大部分應用程式,停頓導致的延遲都是可以忽略不計的。其中的真相就 是,大部分 Eden 區中的物件都能被認為是垃圾,永遠也不會被複制到 Survivor 區或者老年代空間。如果正好相反,Eden 區大部分新生物件不符合 GC 條件,Minor GC 執行時暫停的時間將會長很多。
  所以 Minor GC 的情況就相當清楚了——每次 Minor GC 會清理年輕代的記憶體。

2.Major GC vs Full GC
    Major GC 是清理永久代。Full GC 是清理整個堆空間—包括年輕代和永久代。
很不幸,實際上它還有點複雜且令人困惑。首先,許多 Major GC 是由 Minor GC 觸發的,所以很多情況下將這兩種 GC 分離是不太可能的。另一方面,許多現代垃圾收集機制會清理部分永久代空間,所以使用“cleaning”一詞只是部分正確。
   這使得我們不用去關心到底是叫 Major GC 還是 Full GC,大家應該關注當前的 GC 是否停止了所有應用程式的執行緒,還是能夠併發的處理而不用停掉應用程式的執行緒。

   這種混亂甚至內建到 JVM 標準工具。下面一個例子很好的解釋了我的意思。讓我們比較兩個不同的工具 Concurrent Mark 和 Sweep collector (-XX:+UseConcMarkSweepGC)在 JVM 中執行時輸出的跟蹤記錄。
   關於JVM,需要說明一下的是,目前使用最多的Sun公司的JDK中,自從 1999年的JDK1.2開始直至現在仍在廣泛使用的JDK6,其中預設的虛擬機器都是HotSpot。2009年,Oracle收購Sun,加上之前收購 的EBA公司,Oracle擁有3大虛擬機器中的兩個:JRockit和HotSpot,Oracle也表明了想要整合兩大虛擬機器的意圖,但是目前在新發布 的JDK7中,預設的虛擬機器仍然是HotSpot,因此本文中預設介紹的虛擬機器都是HotSpot,相關機制也主要是指HotSpot的GC機制。

-- GC機制的基本演算法是:分代收集

 新生代:一般是指大批物件產生的快,消亡的也快;老生代:一般是指大批物件產生後,不容易消。
-- 年輕代:
  新生代的主要垃圾回收方法,在新生代中,使用“停止-複製”演算法進行清理,將新生代記憶體分為2部分,1部分 Eden區較大,1部分Survivor比較小,並被劃分為兩個等量的部分。每次進行清理時,將Eden區和一個Survivor中仍然存活的物件拷貝到 另一個Survivor中,然後清理掉Eden和剛才的Survivor。
    這裡也可以發現,停止複製演算法中,用來複制的兩部分並不總是相等的(傳統的停止複製演算法兩部分記憶體相等,但新生代中使用1個大的Eden區和2個小的Survivor區來避免這個問題)
  由於絕大部分的物件都是短命的,甚至存活不到Survivor中,所以,Eden區與Survivor的比例較大,HotSpot預設是 8:1,即分別佔新生代的80%,10%,10%。如果一次回收中,Survivor+Eden中存活下來的記憶體超過了10%,則需要將一部分物件分配到 老年代。用-XX:SurvivorRatio引數來配置Eden區域Survivor區的容量比值,預設是8,代表Eden:Survivor1:Survivor2=8:1:1.

-- 老年代:
    老年代儲存的物件比年輕代多得多,而且不乏大物件,對老年代進行記憶體清理時,如果使用停止-複製演算法,則相當低效。一般,老年代用的演算法是標記-整理演算法,即:標記出仍然存活的物件(存在引用的),將所有存活的物件向一端移動,以保證記憶體的連續。
     在發生Minor GC時,虛擬機器會檢查每次晉升進入老年代的大小是否大於老年代的剩餘空間大小,如果大於,則直接觸發一次Full GC,否則,就檢視是否設 置了-XX:+HandlePromotionFailure(允許擔保失敗),如果允許,則只會進行MinorGC,此時可以容忍記憶體分配失敗;如果不允許,則仍然進行Full GC(這代表著如果設定-XX:+Handle PromotionFailure,則觸發MinorGC就會同時觸發Full GC,哪怕老年代還有很多記憶體,所以,最好不要這樣做)。
    方法區(永久代),永久代的回收有兩種:常量池中的常量,無用的類資訊,常量的回收很簡單,沒有引用了就可以被回收。對於無用的類進行回收,必須保證3點:1.類的所有例項都已經被回收;2.載入類的ClassLoader已經被回收。3.類物件的Class物件沒有被引用(即沒有通過反射引用該類的地方)
   永久代的回收並不是必須的,可以通過引數來設定是否對類進行回收。HotSpot提供-Xnoclassgc進行控制,使用-verbose,-XX:+TraceClassLoading、-XX:+TraceClassUnLoading可以檢視類載入和解除安裝資訊:
     -verbose、-XX:+TraceClassLoading可以在Product版HotSpot中使用;
     -XX:+TraceClassUnLoading需要fastdebug版HotSpot支援。

-- 垃圾收集器

   在介紹垃圾收集器之前,需要明確一點,就是在新生代採用的停止複製演算法中,“停 止(Stop-the-world)”的意義是在回收記憶體時,需要暫停其他所有執行緒的執行。這個是很低效的,現在的各種新生代收集器越來越優化這一點,但仍然只是將停止的時間變短,並未徹底取消停止。

  1. Serial收集器:新生代收集器,使用停止複製演算法,使用一個執行緒進行GC,其它工作執行緒暫停。使用-XX:+UseSerialGC可以使用Serial+Serial Old模式執行進行記憶體回收(這也是虛擬機器在Client模式下執行的預設值)
  2. ParNew收集器:新生代收集器,使用停止複製演算法,Serial收集器的多執行緒版,用多個執行緒進行GC,其它工作執行緒暫停,關注縮短垃圾收集時間。使用-XX:+UseParNewGC開關來控制使用ParNew+Serial Old收集器組合收集記憶體;使用-XX:ParallelGCThreads來設定執行記憶體回收的執行緒數。
  3. Parallel Scavenge 收集器:新生代收集器,使用停止複製演算法,關注CPU吞吐量,即執行使用者程式碼的時間/總時間,比如:JVM執行100分鐘,其中執行使用者程式碼99分鐘,垃圾收集1分鐘,則吞吐量是99%,這種收集器能最高效率的利用CPU,適合執行後臺運算(關注縮短垃圾收集時間的收集器,如CMS,等待時間很少,所以適 合使用者互動,提高使用者體驗)。使用-XX:+UseParallelGC開關控制使用 Parallel Scavenge+Serial Old收集器組合回收垃圾(這也是在Server模式下的預設值);使用-XX:GCTimeRatio來設定使用者執行時間佔總時間的比例,預設99,即 1%的時間用來進行垃圾回收。使用-XX:MaxGCPauseMillis設定GC的最大停頓時間(這個引數只對Parallel Scavenge有效)
  4. Serial Old收集器:老年代收集器,單執行緒收集器,使用標記整理(整理的方法是Sweep清理和Compact壓縮,清理是將廢棄的物件幹掉,只留倖存的物件,壓縮是將移動物件,將空間填滿保證記憶體分為2塊,一塊全是物件,一塊空閒)演算法,使用單執行緒進行GC,其它工作執行緒暫停(注意,在老年代中進行標 記整理演算法清理,也需要暫停其它執行緒),在JDK1.5之前,Serial Old收集器與ParallelScavenge搭配使用。
  5. Parallel Old收集器:老年代收集器,多執行緒,多執行緒機制與Parallel Scavenge差不錯,使用標記整理(與Serial Old不同,這裡的整理是Summary彙總和Compact壓縮,彙總的意思就是將倖存的物件複製到預先準備好的區域,而不是像Sweep(清 理)那樣清理廢棄的物件)演算法,在Parallel Old執行時,仍然需要暫停其它執行緒。Parallel Old在多核計算中很有用。Parallel Old出現後(JDK 1.6),與Parallel Scavenge配合有很好的效果,充分體現Parallel Scavenge收集器吞吐量優先的效果。使用-XX:+UseParallelOldGC開關控制使用Parallel Scavenge +Parallel Old組合收集器進行收集。
  6. CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力於獲取最短回收停頓時間,使用標記清除演算法,多執行緒,優點是併發收集(使用者執行緒可以和GC執行緒同時工作),停頓小。使用-XX:+UseConcMarkSweepGC進行ParNew+CMS+Serial Old進行記憶體回收,優先使用ParNew+CMS(原因見後面),當使用者執行緒記憶體不足時,採用備用方案Serial Old收集。
  CMS收集的方法是:先3次標記,再1次清除,3次標記中前兩次是初始標記和重新標記(此時仍然需要停止(stop the world)), 初始標記(Initial Remark)是標記GC Roots能關聯到的物件(即有引用的物件),停頓時間很短;併發標記(Concurrent remark)是執行GC Roots查詢引用的過程,不需要使用者執行緒停頓;重新標記(Remark)是在初始標記和併發標記期間,有標記變動的那部分仍需要標記,所以加上這一部分 標記的過程,停頓時間比並發標記小得多,但比初始標記稍長。在完成標記之後,就開始併發清除,不需要使用者執行緒停頓。
  所以在CMS清理過程中,只有初始標記和重新標記需要短暫停頓,併發標記和併發清除都不需要暫停使用者執行緒,因此效率很高,很適合高互動的場合。
  CMS也有缺點,它需要消耗額外的CPU和記憶體資源,在CPU和記憶體資源緊張,CPU較少時,會加重系統負擔(CMS預設啟動執行緒數為(CPU數量+3)/4)。
  另外,在併發收集過程中,使用者執行緒仍然在執行,仍然產生記憶體垃圾,所以可能產生“浮動垃圾”,本次無法清理,只能下一次Full GC才清理,因此在GC期間,需要預留足夠的記憶體給使用者執行緒使用。所以使用CMS的收集器並不是老年代滿了才觸發Full GC,而是在使用了一大半(預設68%,即2/3,使用-XX:CMSInitiatingOccupancyFraction來設定)的時候就要進行Full GC,如果使用者執行緒消耗記憶體不是特別大,可以適當調高-XX:CMSInitiatingOccupancyFraction以降低GC次數,提高效能,如果預留的使用者執行緒記憶體不夠,則會觸發Concurrent Mode Failure,此時,將觸發備用方案:使用Serial Old 收集器進行收集,但這樣停頓時間就長了,因此-XX:CMSInitiatingOccupancyFraction不宜設的過大。
  還有,CMS採用的是標記清除演算法,會導致記憶體碎片的產生,可以使用-XX:+UseCMSCompactAtFullCollection來設定是否在Full GC之後進行碎片整理,用-XX:CMSFullGCsBeforeCompaction來設定在執行多少次不壓縮的Full GC之後,來一次帶壓縮的Full GC。
G1收集器:在JDK1.7中正式釋出,與現狀的新生代、老年代概念有很大不同,目前使用較少,不做介紹。
注意併發(Concurrent)和並行(Parallel)的區別:
     併發是指使用者執行緒與GC執行緒同時執行(不一定是並行,可能交替,但總體上是在同時執行的),不需要停頓使用者執行緒(其實在CMS中使用者執行緒還是需要停頓的,只是非常短,GC執行緒在另一個CPU上執行);
     並行收集是指多個GC執行緒並行工作,但此時使用者執行緒是暫停的;所以,Serial和Parallel收集器都是並行的,而CMS收集器是併發的.

-- 垃圾回收器經典演算法:
  1)Reference counting(引用計數)
基本思想是:當物件建立並賦值時該物件的引用計數器置1,每當物件給任意變數賦值時,引用記數+1;一旦退出作用域則引用記數-1。一旦引用記數變為0,則該物件可以被垃圾回收。
引用記數有其相應的優勢:對程式的執行來說,每次操作只需要花費很小塊的時間。這對於不能被過長中斷的實時系統來說有著天然的優勢。
但也有其不足:不能夠檢測到環(兩個物件的互相引用);同時在每次增加或者減少引用記數的時候比較費時間。在現代的垃圾回收演算法中,引用記數已經不再使用。
  2)Mark-sweep(標記清理)
基本思想是:每次從根集出發尋找所有的引用(稱為活物件),每找到一個,則對其做出標記,當追蹤完成之後,所有的未標記物件便是需要回收的垃圾。
也叫追蹤演算法,基於標記並清除。這個垃圾回收步驟分為兩個階段:在標記階段,垃圾回收器遍歷整棵引用樹並標記每一個遇到的物件。在清除階段,未標記的物件被釋放,並使其在記憶體中可用。
  3)Copying collection(複製收集)
基本思想是:將記憶體劃分為兩塊,一塊是當前正在使用;另一塊是當前未用。每次分配時使用當前正在使用記憶體,當無可用記憶體時,對該區域記憶體進行標記,並將標記的物件全部拷貝到當前未用記憶體區,這是反轉兩區域,即當前可用區域變為當前未用,而當前未用變為當前可用,繼續執行該演算法。
拷貝演算法需要停止所有的程式活動,然後開始冗長而繁忙的copy工作。這點是其不利的地方。
  近年來還有兩種演算法:
  4)Generational garbage collection(分代)
其思想依據是:
  (1) 被大多數程式建立的大多數物件有著非常短的生存期。
  (2) 被大多數程式建立的部分物件有著非常長的生存期。
簡單拷貝演算法的主要不足是它們花費了更多的時間去拷貝了一些長期生存的物件。
而分代演算法的基本思想是:將記憶體區域分兩塊(或更多),其中一塊代表年輕代,另一塊代表老的一代。針對不同的特點,對年輕一代的垃圾收集更為頻繁,對老代的收集則較少,每次經過年輕一代的垃圾回收總會有未被收集的活物件,這些活物件經過收集之後會增加成熟度,當成熟度到達一定程度,則將其放進老代記憶體塊中。
分代演算法很好的實現了垃圾回收的動態性,同時避免了記憶體碎片,是目前許多JVM使用的垃圾回收演算法。
  5)Conservative garbage collection(保守)

相關文章