JVM系列:二、JVM記憶體回收
程式計數器、虛擬機器棧、本地方法棧這三個區域的生命週期是和執行緒同步的,並且記憶體分配是在編譯期就知道了,所以在方法結束或執行緒結束時,這三個區域的記憶體自然就回收了。而Java堆和方法區是在程式執行時才動態分配和回收記憶體,垃圾收集器所關注的就是這部分的記憶體。
判斷物件是否死亡
堆中幾乎放著所有的物件例項。垃圾收集器判斷堆中物件是否“死去”有這幾種方法:
1、引用計數法
當有一個地方引用該物件,計數器就加1,引用失效,則計數器減1,當計數器為0時表示物件“死亡”。(Java並沒有使用引用計數法,因為它很難解決物件之間相互引用的問題)
2、根搜尋法
通過一系列“GC Root”物件作為起點,向下搜尋,當
垃圾收集演算法
1、標記-清除演算法
首先標記處所需要回收的物件,完成之後統一回收掉所有被標記的物件。不過它有兩個缺點:效率低、空間浪費(產生大量不連續的記憶體碎片)
2、複製演算法
將記憶體劃分大小相等的兩塊,每次只使用其中一塊,每次回收後將存活的物件複製到另一塊記憶體上。這種執行效率高,代價是要浪費了一半的記憶體空間。
3、標記-整理演算法
該演算法的標記部分和“標記-清除”一樣,不過標記完不是直接清除,而是讓所有存活的物件都向一端移動,然後直接清理掉邊界以為的記憶體。
4、分代收集演算法
當前的商業虛擬機器都是使用該演算法,一般將記憶體分為新生代、老年代,根據各年代不同的特點使用不同的演算法。新生代就使用複製演算法,老年代使用“標記-
垃圾收集器
Hotspot JVM 1.6 的垃圾收集器
兩個收集器之間存在連線,表示可以搭配使用。這些收集器各有特點,我們需要在具體場景下選用最好的收集器。
1、Serial收集器
單執行緒收集器,垃圾收集時其他工作必須暫停。執行時執行緒如下圖所示。
Serial收集器是Client模式下預設的新生代收集器,由於其簡單高效,在桌面應用中使用記憶體一般比較小,用該收集器是不錯的選擇。
Serial/Serial Old收集器
2、ParNew收集器
ParNew收集器是Serial收集器的多執行緒版本,是Sever模式下首選的新生代收集器。除了
剛才說了ParNew是Serial的多執行緒,因此在單CPU下,其實Serial是更好的選擇,不過目前單CPU的機器基本絕跡了。
-XX:+UseParNewGC (或使用+XX:+UseConcMarkSweepGC的預設新生代收集器)
ParNew/Serial Old收集器
3、Parallel Scavenge收集器
它是使用複製演算法的收集器,也是並行的多執行緒收集器。Parallel Scavenge收集器月ParNew的區別主要是它目標是達到可控的吞吐量,而其他的收集器是儘可能縮短使用者執行緒停頓時間。
4、Serial Old收集器
Serial Old是Serial的老年代版本,同樣是單執行緒的,垃圾收集使用“標記-整理”演算法。主要也是在Client模式下使用,在Serial中的圖同時顯示了Serial Old的執行時的執行緒。
5、Parallel Old收集器
Parallel Old是Parallel Scavenge的老年代版本,垃圾收集使用多執行緒和“標記-整理”演算法。在注重吞吐量及CPU資源敏感的場景,可以優先選擇Parallel Scavenge+Parallel Old的組合。
Parallel Scavenge/Parallel Old收集器
6、CMS收集器
CMS(Concurrent Mark Sweep)目標是獲取最短停頓時間的收集器,是目前B/S中服務端最流行的收集器。
CMS是基於“標記-清除”的演算法實現的,可以分為4個步驟:
a. 初始標記:標記GC Roots能關聯到的物件
b. 併發標記:進行GC Roots Tracing,判斷物件是否死亡
c. 重新標記:修正併發標記期間因使用者程式繼續執行,導致標記變動的那些物件
d. 併發清除
CMS收集器
通過上述可以知道“初始標記”和“重新標記”是需要暫停使用者執行緒的,而“併發標記”和“併發清除”是收集器執行緒與使用者執行緒一起工作的。不過1、3步驟耗時較短,主要的2、4步驟是併發執行的,可以說CMS的記憶體回收過程是與使用者執行緒一起執行的。
CMS雖然可以說是併發低停頓收集器,當還是有一些缺點:
l 對CPU資源敏感:由於併發收集,佔用CPU資源而導致程式變慢,總吞吐量降低。
l 無法處理浮動垃圾(標記過程後產生的垃圾),導致可能出現Full GC。所以CMS GC時需要預留一部分空間,即在空間使用到一定比例時就需要GC操作。
l CMS是基於“標記-清除”的演算法,會出現大量的空間碎片。(記憶體整理的過程是無法併發的)
7、G1收集器
G1的目標是能替代CMS收集器,但在JDK7正式釋出之後,仍然沒有擺脫Experimental的標籤。它與CMS相比有兩個顯著的改進:一、G1是基於“標記-整理”演算法實現;二、可以非常精確地控制停頓。
它不再將Java堆分為新生代、老年代,而是分為多個大小固定的區域(Region),跟蹤這些區域,優先回收垃圾最多的區域。
回收策略
上面已經描述了堆記憶體分為三部分:新生代、舊生代、持久代。Java程式啟動的時候,ClassLoader資訊會放入持久代,新建的物件會放入新生代,當新生代滿了(Eden滿了),會引發Minor GC(YGC),YGC後還存活的物件將被移動到舊生代,而當舊生代滿了就會觸發Major GC(Full GC)。
Full GC是整個heap的回收,包括了舊生代和新生代,而且舊生代一般佔儲存空間比較大,收集時間長,頻繁的Full GC會對應用的效能產生較大的影響,所以要儘量減少Full GC的次數。
Minor GC(YGC)
1、大部分物件建立後先進入Eden Space,Eden Space空間不足將會觸發YGC。
2、YGC垃圾回收時,掃描Eden Space和S0進行回收,如果物件還存活,將其複製到S1。如果S1已滿則複製到舊生代
3、掃描S0時,如果物件已經過了幾次掃描還存活(超過turning threshold值),則移到舊生代
4、掃描完畢將Eden Space和S0清空,下次YGC時將S0、S1角色對換,從步驟2開始重複GC操作
這種機制就不需要每次GC都將記憶體所有物件檢查一遍了,避免了像Full GC那樣對應用的效能產生較大的影響。
Major GC(Full GC)
由於舊生代或持久代空間不足等原因,將會觸發Full GC。回收機制根據各收集器而定,上面垃圾收集器部分已經說了具體過程。