1. 程式人生 > >JVM系列:二、JVM記憶體回收

JVM系列:二、JVM記憶體回收

程式計數器、虛擬機器棧、本地方法棧這三個區域的生命週期是和執行緒同步的,並且記憶體分配是在編譯期就知道了,所以在方法結束或執行緒結束時,這三個區域的記憶體自然就回收了。而Java堆和方法區是在程式執行時才動態分配和回收記憶體,垃圾收集器所關注的就是這部分的記憶體。

判斷物件是否死亡

堆中幾乎放著所有的物件例項。垃圾收集器判斷堆中物件是否“死去”有這幾種方法:

1、引用計數法

當有一個地方引用該物件,計數器就加1,引用失效,則計數器減1,當計數器為0時表示物件“死亡”。(Java並沒有使用引用計數法,因為它很難解決物件之間相互引用的問題)

2、根搜尋法

通過一系列“GC Root”物件作為起點,向下搜尋,當

GC Root到這個物件不可達時,表示該物件“死亡”。(主流的程式都是該方法)

垃圾收集演算法

1、標記-清除演算法

首先標記處所需要回收的物件,完成之後統一回收掉所有被標記的物件。不過它有兩個缺點:效率低、空間浪費(產生大量不連續的記憶體碎片)

2、複製演算法

將記憶體劃分大小相等的兩塊,每次只使用其中一塊,每次回收後將存活的物件複製到另一塊記憶體上。這種執行效率高,代價是要浪費了一半的記憶體空間。

3、標記-整理演算法

該演算法的標記部分和“標記-清除”一樣,不過標記完不是直接清除,而是讓所有存活的物件都向一端移動,然後直接清理掉邊界以為的記憶體。

4、分代收集演算法

當前的商業虛擬機器都是使用該演算法,一般將記憶體分為新生代、老年代,根據各年代不同的特點使用不同的演算法。新生代就使用複製演算法,老年代使用“標記-

清除”或“標記-整理”演算法。

垃圾收集器


Hotspot JVM 1.6 的垃圾收集器

兩個收集器之間存在連線,表示可以搭配使用。這些收集器各有特點,我們需要在具體場景下選用最好的收集器。

1、Serial收集器

單執行緒收集器,垃圾收集時其他工作必須暫停。執行時執行緒如下圖所示。

Serial收集器是Client模式下預設的新生代收集器,由於其簡單高效,在桌面應用中使用記憶體一般比較小,用該收集器是不錯的選擇。


Serial/Serial Old收集器

2、ParNew收集器

ParNew收集器是Serial收集器的多執行緒版本,是Sever模式下首選的新生代收集器。除了

Serial外,只有它能和CMS收集器配合,也是使用CMS後預設的新生代收集器。

剛才說了ParNewSerial的多執行緒,因此在單CPU下,其實Serial是更好的選擇,不過目前單CPU的機器基本絕跡了。

-XX:+UseParNewGC (或使用+XX:+UseConcMarkSweepGC的預設新生代收集器)


ParNew/Serial Old收集器

3、Parallel Scavenge收集器

它是使用複製演算法的收集器,也是並行的多執行緒收集器。Parallel Scavenge收集器月ParNew的區別主要是它目標是達到可控的吞吐量,而其他的收集器是儘可能縮短使用者執行緒停頓時間。

4、Serial Old收集器

Serial OldSerial的老年代版本,同樣是單執行緒的,垃圾收集使用“標記-整理”演算法。主要也是在Client模式下使用,在Serial中的圖同時顯示了Serial Old的執行時的執行緒。

5、Parallel Old收集器

Parallel OldParallel Scavenge的老年代版本,垃圾收集使用多執行緒和“標記-整理”演算法。在注重吞吐量及CPU資源敏感的場景,可以優先選擇Parallel Scavenge+Parallel Old的組合。


Parallel Scavenge/Parallel Old收集器

6、CMS收集器

CMSConcurrent Mark Sweep)目標是獲取最短停頓時間的收集器,是目前B/S中服務端最流行的收集器。

CMS是基於“標記-清除”的演算法實現的,可以分為4個步驟:

a. 初始標記:標記GC Roots能關聯到的物件

b. 併發標記:進行GC Roots Tracing,判斷物件是否死亡

c. 重新標記:修正併發標記期間因使用者程式繼續執行,導致標記變動的那些物件

d. 併發清除


CMS收集器

通過上述可以知道“初始標記”和“重新標記”是需要暫停使用者執行緒的,而“併發標記”和“併發清除”是收集器執行緒與使用者執行緒一起工作的。不過13步驟耗時較短,主要的24步驟是併發執行的,可以說CMS的記憶體回收過程是與使用者執行緒一起執行的。

CMS雖然可以說是併發低停頓收集器,當還是有一些缺點:

CPU資源敏感:由於併發收集,佔用CPU資源而導致程式變慢,總吞吐量降低。

無法處理浮動垃圾(標記過程後產生的垃圾),導致可能出現Full GC。所以CMS GC時需要預留一部分空間,即在空間使用到一定比例時就需要GC操作。

CMS是基於“標記-清除”的演算法,會出現大量的空間碎片。(記憶體整理的過程是無法併發的)

7、G1收集器

G1的目標是能替代CMS收集器,但在JDK7正式釋出之後,仍然沒有擺脫Experimental的標籤。它與CMS相比有兩個顯著的改進:一、G1是基於“標記-整理”演算法實現;二、可以非常精確地控制停頓。

它不再將Java堆分為新生代、老年代,而是分為多個大小固定的區域(Region),跟蹤這些區域,優先回收垃圾最多的區域。

回收策略

上面已經描述了堆記憶體分為三部分:新生代、舊生代、持久代。Java程式啟動的時候,ClassLoader資訊會放入持久代,新建的物件會放入新生代,當新生代滿了(Eden滿了),會引發Minor GCYGC),YGC後還存活的物件將被移動到舊生代,而當舊生代滿了就會觸發Major GCFull GC)。

Full GC是整個heap的回收,包括了舊生代和新生代,而且舊生代一般佔儲存空間比較大,收集時間長,頻繁的Full GC會對應用的效能產生較大的影響,所以要儘量減少Full GC的次數。

Minor GCYGC

1、大部分物件建立後先進入Eden SpaceEden Space空間不足將會觸發YGC

2、YGC垃圾回收時,掃描Eden SpaceS0進行回收,如果物件還存活,將其複製到S1。如果S1已滿則複製到舊生代

3、掃描S0時,如果物件已經過了幾次掃描還存活(超過turning threshold值),則移到舊生代

4、掃描完畢將Eden SpaceS0清空,下次YGC時將S0S1角色對換,從步驟2開始重複GC操作

這種機制就不需要每次GC都將記憶體所有物件檢查一遍了,避免了像Full GC那樣對應用的效能產生較大的影響。

Major GCFull GC

由於舊生代或持久代空間不足等原因,將會觸發Full GC。回收機制根據各收集器而定,上面垃圾收集器部分已經說了具體過程。