1. 程式人生 > >垃圾回收與記憶體分配策略總結

垃圾回收與記憶體分配策略總結

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

1、判斷物件的存活

1.1 引用計數法

給物件新增一個引用計數器,每當有一個地方引用了該物件,計數器就加1;當引用失效,計數器就減1;任何時刻的計數器為0的物件就是不可能在被使用的物件。

優點:快,方便,實現簡單,

缺點:物件相互引用時,很難判斷物件是否改回收。

1.2 可達性分析

這個演算法的基本思路就是通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的。

作為GC Roots的物件包括下面幾種:

  • 虛擬機器棧(棧幀中的本地變量表)中引用的物件。
  • 方法區中類靜態屬性引用的物件。
  • 方法區中常量引用的物件。
  • 本地方法棧中JNI(即一般說的Native方法)引用的物件。

2、標記-清除演算法

標記-清除演算法是現代垃圾回收演算法的思想基礎。標記-清除演算法將垃圾回收分為兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先通過根節點,標記所有從根節點開始的可達物件。因此,未被標記的物件就是未被引用的垃圾物件。然後,在清除階段,清除所有未被標記的物件。

 

3、標記-壓縮

標記-壓縮演算法適合用於存活物件較多的場合,如老年代。它在標記-清除演算法的基礎上做了一些優化。和標記-清除演算法一樣,標記-壓縮演算法也首先需要從根節點開始,對所有可達物件做一次標記。但之後,它並不簡單的清理未標記的物件,而是將所有的存活物件壓縮到記憶體的一端。之後,清理邊界外所有的空間。

4、複製演算法

  • 與標記-清除演算法相比,複製演算法是一種相對高效的回收方法
  • 不適用於存活物件較多的場合,如老年代
  • 將原有的記憶體空間分為兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的記憶體中的存活物件複製到未使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,完成垃圾回收。

複製演算法的最大問題是:空間浪費。

5、分代思想

依據物件的存活週期進行分類,短命物件歸為新生代,長命物件歸為老年代。 根據不同代的特點,選取合適的收集演算法。 少量物件存活,適合複製演算法。 大量物件存活,適合標記清理或者標記壓縮。

6、序列收集器

特點:穩定、效率高、 可能會產生較長的停頓 。

引數 -XX:+UseSerialGC:

新生代、老年代使用序列回收, 新生代複製演算法,老年代標記-壓縮。

7、ParNew 並行收集器

gc引數 -XX:+UseParNewGC

新生代並行,老年代序列。Serial收集器新生代的並行版本,複製演算法多執行緒,需要多核支援 。

-XX:ParallelGCThreads 限制執行緒數量,最好與cpu數量相當,避免過多的執行緒,影響垃圾收集效能。

8、Parallel收集器

類似ParNew,新生代複製演算法,老年代標記-壓縮,更加關注吞吐量。

-XX:+UseParallelGC 使用Parallel收集器+ 老年代序列

-XX:+UseParallelOldGC 使用Parallel收集器+ 並行老年代

-XX:MaxGCPauseMills 最大停頓時間,單位毫秒 GC盡力保證回收時間不超過設定值

-XX:GCTimeRatio   0-100的取值範圍,設定垃圾回收時間佔程式執行時間的比值。預設99,即最大允許1%時間做GC。

這兩個引數是矛盾的,因為停頓時間和吞吐量不可能同時調優。

9、CMS收集器

Concurrent Mark Sweep 併發標記清除,老年代收集器(新生代使用ParNew)

引數:-XX:+UseConcMarkSweepGC

CMS執行過程比較複雜,著重實現了標記的過程可分為

1、初始標記- 根可以直接關聯到的物件 速度快

2、併發標記-(和使用者執行緒一起) 主要標記過程,標記全部物件

3、重新標記-由於併發標記時,使用者執行緒依然執行,因此在正式清理前,再做修正

4、併發清除-(和使用者執行緒一起)基於標記結果,直接清理物件。

 

10、G1回收器

1、並行與併發:能充分利用多CPU、多核環境下的硬體優勢;可以並行來縮短"Stop The World"停頓時間;也可以併發讓垃圾收集與使用者程式同時進行;

2、結合多種垃圾收集演算法,空間整合,不產生碎片。從整體看,是基於標記-整理演算法;從區域性(兩個Region間)看,是基於複製演算法;

3、分代收集:收集範圍包括新生代和老年代。能獨立管理整個GC堆(新生代和老年代),而不需要與其他收集器搭配;能夠採用不同方式處理不同時期的物件;雖然保留分代概念,但Java堆的記憶體佈局有很大差別;將整個堆劃分為多個大小相等的獨立區域(Region);新生代和老年代不再是物理隔離,它們都是一部分Region(不需要連續)的集合;

4、可預測的停頓:低停頓的同時實現高吞吐量,G1除了追求低停頓處,還能建立可預測的停頓時間模型;可以明確指定M毫秒時間片內,垃圾收集消耗的時間不超過N毫秒。

關於g1的詳細可以細讀這篇文章https://www.jianshu.com/p/0f1f5adffdc1

設定引數:

"-XX:+UseG1GC":指定使用G1收集器;

"-XX:InitiatingHeapOccupancyPercent":當整個Java堆的佔用率達到引數值時,開始併發標記階段;預設為45;

"-XX:MaxGCPauseMillis":為G1設定暫停時間目標,預設值為200毫秒;

"-XX:G1HeapRegionSize":設定每個Region大小,範圍1MB到32MB;目標是在最小Java堆時可以擁有約2048個Region;

11、記憶體分配與回收策略

  • 物件優先在Eden分配,如果說Eden記憶體空間不足,就會發生Minor GC
  • 大物件直接進入老年代,大物件:需要大量連續記憶體空間的Java物件,比如很長的字串和大型陣列。-XX:PretenureSizeThreshold 引數,大於這個數量直接在老年代分配,預設為0 ,表示絕不會直接分配在老年代。
  • 長期存活的物件將進入老年代,預設15歲,-XX:MaxTenuringThreshold調整。
  • 動態物件年齡判定,為了能更好地適應不同程式的記憶體狀況,虛擬機器並不是永遠地要求物件的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡
  • 空間分配擔保:新生代中有大量的物件存活,survivor空間不夠,當出現大量物件在MinorGC後仍然存活的情況(最極端的情況就是記憶體回收後新生代中所有物件都存活),就需要老年代進行分配擔保,把Survivor無法容納的物件直接進入老年代.只要老年代的連續空間大於新生代物件的總大小或者歷次晉升的平均大小,就進行Minor GC,否則FullGC。

12、吞吐量優先和響應優先的垃圾收集器如何選擇?

(1) 吞吐量優先的並行收集器 

引數配置: 

1、 -Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:ParallelGCThreads=8 

說明:選擇Parallel Scavenge收集器,然後配置多少個執行緒進行回收,最好與處理器數目相等。

2、-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:ParallelGCThreads=8 -XX:+UseParallelOldGC 

說明:配置老年代使用Parallel Old

3、-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:MaxGCPauseMills=100 

說明:設定每次年輕代垃圾回收的最長時間。如果不能滿足,那麼就會調整年輕代大小,滿足這個設定。

4、-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseParallelGC -XX:MaxGCPauseMills=100 -XX:+UseAdaptiveSizePolicy 

說明:並行收集器會自動選擇年輕代區大小和Survivor區的比例。

(2)響應時間優先的併發收集器 

1、 -Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 

說明:設定老年代的收集器是CMS,年輕代是ParNew

2、-Xmx4g -Xms4g -Xmn2g -Xss200k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection 

說明:首先設定執行多少次FullGC後對記憶體空間進行壓縮,整理。同時開啟對年老代的壓縮(會影響效能)