天天看點

【深入了解JVM】JVM垃圾回收機制

最近又回頭翻了翻《java程式設計思想》。有關垃圾回收機制的問題還沒有總結過,剛好今天周六,總結一下吧。

1、垃圾回收的目的

垃圾回收的目的是查找和回收(清理)無用的對象,以便讓JVM更有效的使用記憶體。由于有垃圾回收機制,java中的對象不再有“作用域”的概念,隻有對象的引用才有“作用域”。垃圾回收可以有效的防止記憶體洩漏,有效的使用空閑的記憶體。

注意:記憶體洩漏是指該記憶體空間使用完畢之後未回收,在不涉及複雜資料結構的一般情況下,java的記憶體洩漏表現為一個記憶體對象的生命周期超出了程式需要它的時間長度,我們有時也将其稱為“對象遊離”。

範圍:要回收哪些區域

在JVM五種記憶體模型中,有三個是不需要進行垃圾回收的:程式計數器、JVM棧、本地方法棧。因為它們的生命周期是和線程同步的,随着線程的銷毀,它們占用的記憶體會自動釋放,是以隻有方法區和堆需要進行GC。

2、垃圾回收的類型

由于對象進行了分代處理,是以垃圾回收區域、時間也不一樣。GC有兩種類型:Scavenge GC和Full GC。

2.1、Scavenge GC

一般情況下,當新對象生成,并且在Eden申請空間失敗時,就會觸發Scavenge GC,對Eden區域進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區。然後整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。因為大部分對象都是從Eden區開始的,同時Eden區不會配置設定的很大,是以Eden區的GC會頻繁進行。因而,一般在這裡需要使用速度快、效率高的算法,使Eden去能盡快空閑出來。

2.2、Full GC

對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個堆進行回收,是以比Scavenge GC要慢,是以應該盡可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對于FullGC的調節。有如下原因可能導緻Full GC:

1.年老代(Tenured)被寫滿

2.持久代(Perm)被寫滿

3.System.gc()被顯示調用

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

3、如何判斷對象已死

所有的垃圾收集算法都面臨同一個問題,那就是找出應用程式不可到達的記憶體塊,将其釋放,這裡面得不可到達主要是指應用程式已經沒有記憶體塊的引用了, 在JAVA中,某個對象對應用程式是可到達的是指:這個對象被根(根主要是指類的靜态變量,或者活躍在所有線程棧的對象的引用)引用或者對象被另一個可到達的對象引用。

3.1 引用計數算法

引用計數是最簡單直接的一種方式,這種方式在每一個對象中增加一個引用的計數,這個計數代表目前程式有多少個引用引用了此對象,如果此對象的引用計數變為0,那麼此對象就可以作為垃圾收集器的目标對象來收集。

優點:簡單,直接,不需要暫停整個應用;

缺點:

1.需要編譯器的配合,編譯器要生成特殊的指令來進行引用計數的操作;

2.不能處理循環引用的問題

是以這種方法是垃圾收集的早期政策,現在很少使用。Sun的JVM并沒有采用引用計數算法來進行垃圾回收,是基于根搜尋算法的。看下面這段代碼:

public class Main {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();

        object1.object = object2;
        object2.object = object1;

        object1 = null;
        object2 = null;
    }
}

class MyObject{
    public Object object = null;
}
           

最後面兩句将object1和object2指派為null,也就是說object1和object2指向的對象已經不可能再被通路,但是由于它們互相引用對方,導緻它們的引用計數都不為0,那麼垃圾收集器就永遠不會回收它們。

為了解決這個問題,在Java中采取了 可達性分析法。即為下面的根搜尋算法。該方法的基本思想是通過一系列的“GC Roots”對象作為起點進行搜尋,如果在“GC Roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的,不過要注意的是被判定為不可達的對象不一定就會成為可回收對象。被判定為不可達的對象要成為可回收對象必須至少經曆兩次标記過程,如果在這兩次标記過程中仍然沒有逃脫成為可回收對象的可能性,則基本上就真的成為可回收對象了。

3.2 根搜尋算法

通過一系列的名為“GC Root”的對象作為起點,從這些節點向下搜尋,搜尋所走過的路徑稱為引用鍊(Reference Chain),當一個對象到GC Root沒有任何引用鍊相連時,則該對象不可達,該對象是不可使用的,垃圾收集器将回收其所占的記憶體。

在java語言中,可作為GCRoot的對象包括以下幾種對象:

a. java虛拟機棧(棧幀中的本地變量表)中的引用的對象。

b.方法區中的類靜态屬性引用的對象。

c.方法區中的常量引用的對象。

d.本地方法棧中JNI本地方法的引用對象。

判斷無用的類:

(1).該類的所有執行個體都已經被回收,即java堆中不存在該類的執行個體對象。

(2).加載該類的類加載器已經被回收。

(3).該類所對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射機制通路該類的方法。

3.3 四種引用

GC在收集一個對象的時候會判斷是否有引用指向對象,在JAVA中的引用主要有四種:

⑴ 強引用(Strong Reference)

強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當記憶體空間不足,Java虛拟機甯願抛出OutOfMemoryError錯誤,使程式異常終止,也不會靠随意回收具有強引用的對象來解決記憶體不足的問題。

⑵ 軟引用(Soft Reference)

如果一個對象隻具有軟引用,則記憶體空間足夠,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些對象的記憶體。隻要垃圾回收器沒有回收它,該對象就可以被程式使用。軟引用可用來實作記憶體敏感的高速緩存。

下面舉個例子,假如有一個應用需要讀取大量的本地圖檔,如果每次讀取圖檔都從硬碟讀取,則會嚴重影響性能,但是如果全部加載到記憶體當中,又有可能造成記憶體溢出,此時使用軟引用可以解決這個問題。

設計思路是:用一個HashMap來儲存圖檔的路徑和相應圖檔對象關聯的軟引用之間的映射關系,在記憶體不足時,JVM會自動回收這些緩存圖檔對象所占用的空間,進而有效地避免了記憶體溢出的問題。

軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛拟機就會把這個軟引用加入到與之關聯的引用隊列中。

(3)弱引用(Weak Reference)

弱引用與軟引用的差別在于:隻具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的記憶體區域的過程中,一旦發現了隻具有弱引用的對象,不管目前記憶體空間足夠與否,都會回收它的記憶體。不過,由于垃圾回收器是一個優先級很低的線程,是以不一定會很快發現那些隻具有弱引用的對象。

(4)虛引用(Phantom Reference)

“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。

虛引用主要用于檢測對象是否已經從記憶體中删除,跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個差別在于:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的記憶體之前,把這個虛引用加入到與之關聯的引用隊列中。

最後總結一下平常遇到的比較常見的将對象判定為可回收對象的情況:

1)顯示地将某個引用指派為null或者将已經指向某個對象的引用指向新的對象,比如下面的代碼:

Object obj = new Object();
obj = null;
Object obj1 = new Object();
Object obj2 = new Object();
obj1 = obj2;
           

2)局部引用所指向的對象,比如下面這段代碼:

void fun() {

.....
    for(int i=;i<;i++) {
        Object obj = new Object();
        System.out.println(obj.getClass());
    }    
}
           

循環每執行完一次,生成的Object對象都會成為可回收的對象。

3)隻有弱引用與其關聯的對象,比如:

WeakReference<String> wr = new WeakReference<String>(new String("world"));
           

4、JVM中的垃圾收集政策/算法

在确定了哪些垃圾可以被回收後,垃圾收集器要做的事情就是開始進行垃圾回收,但是這裡面涉及到一個問題是:如何高效地進行垃圾回收。由于Java虛拟機規範并沒有對如何實作垃圾收集器做出明确的規定,是以各個廠商的虛拟機可以采用不同的方式來實作垃圾收集器,是以在此隻讨論幾種常見的垃圾收集算法的核心思想。

4.1 标記-清除算法(Mark-Sweep)

【深入了解JVM】JVM垃圾回收機制

标記清除收集器停止所有的工作,從根掃描每個活躍的對象,然後标記掃描過的對象,标記完成以後,清除那些沒有被标記的對象。

優點:

1 解決循環引用的問題

2 不需要編譯器的配合,進而就不執行額外的指令

缺點:

1. 每個活躍的對象都要進行掃描,收集暫停的時間比較長。

2.标記-清除算法采用從根集合進行掃描,對存活的對象對象标記,标記完畢後,再掃描整個空間中未被标記的對象,進行回收,如上圖所示。

标記-清除算法不需要進行對象的移動,并且僅對不存活的對象進行處理,在存活對象比較多的情況下極為高效,但由于标記-清除算法直接回收不存活的對象,是以會造成記憶體碎片。

4.2 複制算法(Copying)

【深入了解JVM】JVM垃圾回收機制

複制收集器将記憶體分為兩塊一樣大小空間,某一個時刻,隻有一個空間處于活躍的狀态,當活躍的空間滿的時候,GC就會将活躍的對象複制到未使用的空間中去,原來不活躍的空間就變為了活躍的空間。

優點:

隻掃描可以到達的對象,不需要掃描所有的對象,進而減少了應用暫停的時間

缺點:

1.需要額外的空間消耗,某一個時刻,總是有一塊記憶體處于未使用狀态

2.複制對象需要一定的開銷

複制算法采用從根集合掃描,并将存活對象複制到一塊新的,沒有使用過的空間中,這種算法當空間存活的對象比較少時,極為高效,但是帶來的成本是需要一塊記憶體交換空間用于進行對象的移動。

4.3 标記-整理算法(Mark-Compact)

【深入了解JVM】JVM垃圾回收機制

标記整理收集器汲取了标記清除和複制收集器的優點,它分兩個階段執行,在第一個階段,首先掃描所有活躍的對象,并标記所有活躍的對象,第二個階段首先清除未标記的對象,然後将活躍的的對象複制到堆得底部

該算法極大的減少了記憶體碎片,并且不需要像複制算法一樣需要兩倍的空間。

标記-整理算法采用标記-清除算法一樣的方式進行對象的标記,但在清除時不同,在回收不存活的對象占用的空間後,會将所有的存活對象往左端空閑空間移動,并更新對應的指針。标記-整理算法是在标記-清除算法的基礎上,又進行了對象的移動,是以成本更高,但是卻解決了記憶體碎片的問題。

4.4 分代回收算法(Generational Collection)

垃圾分代回收算法(GenerationalCollecting)基于對對象生命周期分析後得出的垃圾回收算法。

因為我們前面有介紹,記憶體主要被分為三塊,新生代、舊生代、持久代。三代的特點不同,造就了他們所用的GC算法不同,新生代适合那些生命周期較短,頻繁建立及銷毀的對象,舊生代适合生命周期相對較長的對象,持久代在Sun HotSpot中就是指方法區(有些JVM中根本就沒有持久代這中說法)。首先介紹下新生代、舊生代、持久代的概念及特點。

【深入了解JVM】JVM垃圾回收機制

(1)Young(年輕代、新生代):JVM specification中的 Heap的一部份年輕代分三個區。一個Eden區,兩個Survivor區。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象将被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象将被複制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複制過來的并且此時還存活的對象,将被複制舊生代。需要注意,Survivor的兩個區是對稱的,沒先後關系,是以同一個區中可能同時存在從Eden複制過來對象,和從前一個Survivor複制過來的對象,而複制到年老區的隻有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。

新生代使用複制算法和标記-清除垃圾收集算法,新生代中98%的對象是朝生夕死的短生命周期對象,是以不需要将新生代劃分為容量大小相等的兩部分記憶體,而是将新生代分為Eden區,Survivor from(Survivor 0)和Survivor to(Survivor1)三部分,其占新生代記憶體容量預設比例分别為8:1:1,其中Survivor from和Survivor to總有一個區域是空白,隻有Eden和其中一個Survivor總共90%的新生代容量用于為新建立的對象配置設定記憶體,隻有10%的Survivor記憶體浪費,當新生代記憶體空間不足需要進行垃圾回收時,仍然存活的對象被複制到空白的Survivor記憶體區域中,Eden和非空白的Survivor進行标記-清理回收,兩個Survivor區域是輪換的。

如果空白Survivor空間無法存放下仍然存活的對象時,使用記憶體配置設定擔保機制,直接将新生代依然存活的對象複制到年老代記憶體中,同時對于建立大對象時,如果新生代中無足夠的連續記憶體時,也直接在年老代中配置設定記憶體空間。

Java虛拟機對新生代的垃圾回收稱為Minor GC,次數比較頻繁,每次回收時間也比較短。

使用java虛拟機-Xmn參數可以指定新生代記憶體大小。

(2)Tenured(年老代、舊生代):JVMspecification中的 Heap的一部份年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。

年老代中的對象一般都是長生命周期對象,對象的存活率比較高,是以在年老代中使用标記-整理垃圾回收算法。

Java虛拟機對年老代的垃圾回收稱為MajorGC/Full GC,次數相對比較少,每次回收的時間也比較長。

java虛拟機-Xms參數可以指定最小記憶體大小,-Xmx參數可以指定最大記憶體大小,這兩個參數分别減去Xmn參數指定的新生代記憶體大小,可以計算出年老代最小和最大記憶體容量。

(3)Perm(持久代、永久代): JVM specification中的 Method area 用于存放靜态檔案,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動态生成或者調用一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些運作過程中新增的類。

java虛拟機記憶體中的方法區在SunHotSpot虛拟機中被稱為永久代,是被各個線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯後的代碼等資料。永久代垃圾回收比較少,效率也比較低,但是也必須進行垃圾回收,否則會永久代記憶體不夠用時仍然會抛出OutOfMemoryError異常。

永久代也使用标記-整理算法進行垃圾回收,java虛拟機參數-XX:PermSize和-XX:MaxPermSize可以設定永久代的初始大小和最大容量。

5、垃圾回收過程

上面我們看了JVM的記憶體分區管理,現在我們來看JVM的垃圾回收工作是怎樣運作的。

首先當啟動J2EE應用伺服器時,JVM随之啟動,并将JDK的類和接口,應用伺服器運作時需要的類和接口以及J2EE應用的類和接口定義檔案也及編譯後的Class檔案或JAR包中的Class檔案裝載到JVM的永久存儲區。在Eden Space中建立JVM,存儲應用伺服器運作時必須的JAVA對象,建立J2EE應用啟動時必須建立的JAVA對象;J2EE應用啟動完畢,可對外提供服務。

JVM在Eden區根據使用者的每次請求建立相應的JAVA對象,當Eden區的空間不足以用來建立新JAVA對象的時候,JVM的垃圾回收器執行對Eden區的垃圾回收工作,銷毀那些不再被其他對象引用的JAVA對象(如果該對象僅僅被一個沒有其他對象引用的對象引用的話,此對象也被歸為沒有存在的必要,依此類推),并将那些被其他對象所引用的JAVA對象移動到Survivor0區。

如果Survivor0區有足夠空間存放則直接放到Survivor0區;如果Survivor0區沒有足夠空間存放,則JVM的垃圾回收器執行對Survivor0區的垃圾回收工作,銷毀那些不再被其他對象引用的JAVA對象,并将那些被其他對象所引用的JAVA對象移動到Survivor1區。

如果Survivor1區有足夠空間存放則直接放到Survivor1區;如果Survivor1區沒有足夠空間存放,則JVM的垃圾回收器執行對Survivor1區的垃圾回收工作,銷毀那些不再被其他對象引用的JAVA對象,并将那些被其他對象所引用的JAVA對象移動到年老區。

如果年老區有足夠空間存放則直接放到年老區;如果年老區沒有足夠空間存放,則JVM的垃圾回收器執行對年老區的垃圾回收工作,銷毀那些不再被其他對象引用的JAVA對象,并保留那些被其他對象所引用的JAVA對象。

如果到最後年老區,Survivor1區,Survivor0區和Eden區都沒有空間的話,則JVM會報告“JVM堆空間溢出(java.lang.OutOfMemoryError: Java heap space)”,也即是在堆空間沒有空間來建立對象。

這就是JVM的記憶體分區管理,相比不分區來說;一般情況下,垃圾回收的速度要快很多;因為在沒有必要的時候不用掃描整片記憶體而節省了大量時間。

6、對象的空間配置設定和晉升:

(1)對象優先在Eden上配置設定

對象的記憶體配置設定,往大方向上講就是在堆上配置設定,對象主要配置設定在新生代的Eden Space和From Space,少數情況下會直接配置設定在老年代。如果新生代的Eden Space和From Space的空間不足,則會發起一次GC,如果進行了GC之後,Eden Space和From Space能夠容納該對象就放在Eden Space和From Space。在GC的過程中,會将Eden Space和From Space中的存活對象移動到To Space,然後将Eden Space和From Space進行清理。如果在清理的過程中,To Space無法足夠來存儲某個對象,就會将該對象移動到老年代中。在進行了GC之後,使用的便是Eden space和To Space了,下次GC時會将存活對象複制到From Space,如此反複循環。當對象在Survivor區躲過一次GC的話,其對象年齡便會加1,預設情況下,如果對象年齡達到15歲,就會移動到老年代中。

(2)大對象直接進入老年代

所謂的大對象是指需要大量連續存儲空間的對象,最常見的一種大對象就是大數組,比如:

byte[] data = new byte[**]
           

這種一般會直接在老年代配置設定存儲空間。

當然配置設定的規則并不是百分之百固定的,這要取決于目前使用的是哪種垃圾收集器組合和JVM的相關參數。

虛拟機提供了-XX:PretenureSizeThreshold參數,大于這個參數值的對象将直接配置設定到老年代中。因為新生代采用的是标記-複制政策,在Eden中配置設定大對象将會導緻Eden區和兩個Survivor區之間大量的記憶體拷貝。

(3)長期存活的對象将進入老年代

對象在Survivor區中每熬過一次MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(預設為15歲)時,就會晉升到老年代中。

7、觸發:何時開始GC

Minor GC(新生代回收)的觸發條件比較簡單,Eden空間不足就開始進行Minor GC回收新生代。

而Full GC(老年代回收,一般伴随一次MinorGC)則有幾種觸發條件:

(1)老年代空間不足

(2)PermSpace空間不足

(3)統計得到的MinorGC晉升到老年代的平均大小大于老年代的剩餘空間

這裡注意一點:PermSpace并不等同于方法區,隻不過是HotspotJVM用PermSpace來實作方法區而已,有些虛拟機沒有PermSpace而用其他機制來實作方法區。

8、JVM中的回收器類型

8.1 串行回收器(Serial Collector)

單線程執行回收操作,回收期間暫停所有應用線程的執行,client模式下的預設回收器:

(1)年輕代的回收算法(Minor Collection):把Eden區的存活對象移到To區,To區裝不下直接移到年老代,把From區的移到To區,To區裝不下直接移到年老代,From區裡面年齡很大的更新到年老代。 回收結束之後,Eden和From區都為空,此時把From和To的功能互換,From變To,To變From,每一輪回收之前To都是空的。設計的選型為複制。

(2)年老代的回收算法(Full Collection):年老代的回收分為三個步驟,标記(Mark)、清除(Sweep)、合并(Compact)。标記階段把所有存活的對象标記出來,清除階段釋放所有死亡的對象,合并階段把所有活着的對象合并到年老代的前部分,把空閑的片段都留到後面。設計的選型為合并,減少記憶體的碎片。

8.2 并行回收器(Parallel Collector)

使用多個線程同時進行垃圾回收,多核環境裡面可以充分的利用CPU資源,減少回收時間,增加JVM生産率,Server模式下的預設回收器。與串行回收器相同,回收期間暫停所有應用線程的執行。

(1)年輕代的回收算法(Minor Collection):使用多個線程回收垃圾,每一個線程的算法與串行回收器相同。

(2)年老代的回收算法(Full Collection):年老代依然是單線程的,與串行回收器相同。

8.3 并行合并收集器(Parallel Compacting Collection)

年輕代和年老代的回收都是用多線程處理。與并行回收器相比,年老代的回收時間更短,進而減少了暫停時間間隔(Pause time)。

(1)年輕代的回收算法(Minor Collection):與并行回收器(ParallelCollector)相同

(2)年老代的回收算法(Full Collection) :年老代分為三個步驟,标記、統計、合并。這裡用到分的思想,把年老代劃分為很多個固定大小的區(region)。标記階段,把所有存活的對象劃分為N組(應該與回收線程數相同),每一個線程獨立的負責自己那一組,标記存活對象的位置以及所在區(Region)的存活率資訊,标記為并行的。統計階段,統計每一個區(Region)的存活率,原則上靠前面的存活率較高,從前到後,找到值得合并的開始位置(絕大多數對象都存活的區不值得合并),統計階段是串行的(單線程)。合并階段,依據統計階段的資訊,多線程并行的把存活的對象從一個區(Region)複制到另外一個區(Region)。

8.4 并發标記清除回收器(Concurrent Mark-Sweep Collector)

又名低延時收集器(Low-latencyCollector),通過各種手段使得應用程式被挂起的時間最短。基本與應用程式并發地執行回收操作,沒有合并和複制操作。

(1)年輕代的回收算法(Minor Collection):與并行回收器(ParallelCollector)相同

(2)年老代的回收算法(Full Collection) :分為四個步驟,初始标記(Initial Mark)、并發标記(ConcurrentMark)、再次标記(Remark)、以及并發清理(Concurrent Sweep)。特别注意,沒有合并操作,是以會有碎片。

初始化階段: 暫停應用線程,找出所有存活的對象,耗時比較短,回收器使用單線程。

并發标記階段: 回收器标記操作與應用并發運作,回收器使用單線程标記存活對象。

再次标記:并發标記階段由于應用程式也在運作,這個過程中可能新增或者修改對象。是以再次暫停應用線程,找出所有修改的對象,使用多線程标記。

并發清理:回收器清理操作與應用并發運作,回收器使用單線程清理死亡對象。

8.5 幾種典型的垃圾收集器

垃圾收集算法是 記憶體回收的理論基礎,而垃圾收集器就是記憶體回收的具體實作。下面介紹一下HotSpot(JDK 7)虛拟機提供的幾種垃圾收集器,使用者可以根據自己的需求組合出各個年代使用的收集器。

(1)Serial/Serial Old

  Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,并且在它進行垃圾收集時,必須暫停所有使用者線程。Serial收集器是針對新生代的收集器,采用的是Copying算法,Serial Old收集器是針對老年代的收集器,采用的是Mark-Compact算法。它的優點是實作簡單高效,但是缺點是會給使用者帶來停頓。

(2)ParNew

  ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。

(3)Parallel Scavenge

  Parallel Scavenge收集器是一個新生代的多線程收集器(并行收集器),它在回收期間不需要暫停其他使用者線程,其采用的是Copying算法,該收集器與前兩個收集器有所不同,它主要是為了達到一個可控的吞吐量。

(4)Parallel Old

  Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多線程和Mark-Compact算法。

(5)CMS

  CMS(Current Mark Sweep)收集器是一種以擷取最短回收停頓時間為目标的收集器,它是一種并發收集器,采用的是Mark-Sweep算法。

(6)G1

  G1收集器是當今收集器技術發展最前沿的成果,它是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。是以它是一款并行與并發收集器,并且它能建立可預測的停頓時間模型。

9、使用垃圾收集器要注意的地方

下面将提出一些有關垃圾收集器要注意的地方,垃圾收集器知識很多,下面隻列出一部分必要的知識:

(1)每個對象隻能調用finalize( )方法一次。如果在finalize( )方法執行時産生異常(exception),則該對象仍可以被垃圾收集器收集。

(2)垃圾收集器跟蹤每一個對象,收集那些不可觸及的對象(即該對象不再被程式引用 了),回收其占有的記憶體空間。但在進行垃圾收集的時候,垃圾收集器會調用該對象的finalize( )方法(如果有)。如果在finalize()方法中,又使得該對象被程式引用(俗稱複活了),則該對象就變成了可觸及的對象,暫時不會被垃圾收集了。但是由于每個對象隻能調用一次finalize( )方法,是以每個對象也隻可能 “複活 “一次。

(3)Java語言允許程式員為任何方法添加finalize( )方法,該方法會在垃圾收集器交換回收對象之前被調用。但不要過分依賴該方法對系統資源進行回收和再利用,因為該方法調用後的執行結果是不可預知的。

(4)垃圾收集器不可以被強制執行,但程式員可以通過調研System.gc方法來建議執行垃圾收集。記住,隻是建議。一般不建議自己寫System.gc,因為會加大垃圾收集工作量。