1. 程式人生 > 實用技巧 >JVM的垃圾回收機制及收集器

JVM的垃圾回收機制及收集器

1 如何確定一個物件是垃圾?

想要進行垃圾回收,得先知道什麼樣的物件是垃圾

1.1 引用計數法

引用計數法的邏輯是:在堆中儲存物件時,在物件頭處維護一個counter計數器,如果一個物件增加了一個引用與之相連,則將counter++。如果一個引用關係失效則counter–。如果一個物件的counter變為0,則說明該物件已經被廢棄,不處於存活狀態。 弊端:容易產生”孤島“,即如果AB相互持有引用,導致永遠不能被回收。

1.2 可達性分析

通過GC Root的物件,開始向下尋找,看某個物件是否可達。 能作為GC Root:類載入器、Thread、虛擬機器棧的本地變量表、static成員、常量引用,本地方法棧的變數等。 如下圖所示,通過GC Root為節點,向下查詢,當一個物件沒有被GC Root的引用鏈相連時,將被標記為可回收物件。Object5,、6、7雖說互相持有引用,但是到GC Root是不可達的,所以將會被判斷為可回收物件。

福利福利 福利免費領取Java架構技能地圖 注意了是免費送

​、

免費領取 要的+V 領取

2 垃圾回收演算法

已經能夠確定一個物件為垃圾後,接下來將要考慮的就是回收,那麼如何回收呢?

2.1 標記-清除(Mark-Sweep)

  • 標記
找出記憶體中需要回收的物件,並標記出來

  • 清除
清除掉被標記需要回收的物件,釋放出對應的記憶體空間

**缺點:**
  1. 標記和清除兩個過程都比較耗時,效率不高
  2. 會產生大量不連續的記憶體碎片,空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作

2.2 複製(Copying)

將記憶體劃分為兩塊相等的區域,每次只使用其中一塊,如下圖所示:

當其中一塊記憶體使用完了,就將還存活的物件複製到另外一塊上面,然後把已經使用過的記憶體空間一次清除掉。

** 缺點:**空間利用率降低

2.3 標記-整理(Mark-Compact)

標記過程仍然與"標記-清除"演算法一樣,但是後續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體

讓所有存活的物件都向一端移動,清理掉邊界意外的記憶體。

2.4 分代收集演算法

  • Young區:複製演算法(物件在被分配之後,可能生命週期比較短,Young區複製效率比較高)
  • Old區:標記清除或標記整理(Old區物件存活時間比較長,複製來複制去沒必要,不如做個標記再清理)

3 垃圾收集器

如果說收集演算法是垃圾回收的方法論,那麼垃圾收集器就是對其的落地

3.1 Serial收集器

新生代的收集器,早期JDK版本的新生代收集的唯一選擇 它是一種單執行緒的收集器,另外進行垃圾收集的時候會暫停其他執行緒。 優點:簡單高效,擁有很高的單執行緒手機效率 缺點:需要暫停其他執行緒 演算法:複製演算法 使用範圍:新生代 應用:Client模式下的預設新生代收集器

3.2 ParNew收集器

新生代的收集器,可以把它理解為Serial收集器的多執行緒版本 優點:在多CPU時,比Serial效率高。 缺點:收集過程暫停所有應用程式執行緒,單CPU時比Serial效率差。 演算法:複製演算法 適用範圍:新生代 應用:執行在Server模式下的虛擬機器中首選的新生代收集器

3.3 Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器,它也是使用複製演算法的收集器,又是並行的多執行緒收集器,看上去和ParNew一樣,但是Parallel Scanvenge更關注系統的吞吐量 。 吞吐量=執行使用者程式碼的時間/(執行使用者程式碼的時間+垃圾收集時間) 比如虛擬機器總共運行了100分鐘,垃圾收集時間用了1分鐘,吞吐量=(100-1)/100=99%。若吞吐量越大,意味著垃圾收集的時間越短,則使用者程式碼可以充分利用CPU資源,儘快完成程式的運算任務
-XX:MaxGCPauseMillis控制最大的垃圾收集停頓時間,
-XX:GC Time Ratio直接設定吞吐量的大小
複製程式碼

3.4 Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,也是一個單執行緒收集器,不同的是採用"標記-整理演算法",執行過程和Serial收集器一樣。

3.5 Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多執行緒和"標記-整理演算法"進行垃圾回收。吞吐量優先。

3.6 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取 最短回收停頓時間 為目標的收集器。 採用的是"標記-清除演算法",整個過程分為4步
  1. 初始標記 CMS initial mark 標記GC Roots能關聯到的物件 Stop The World--->速度很快
  2. 併發標記 CMS concurrent mark 進行GC Roots Tracing
  3. 重新標記 CMS remark 修改併發標記因使用者程式變動的內容 Stop TheWorld
  4. 併發清除 CMS concurrent sweep
由於整個過程中,併發標記和併發清除,收集器執行緒可以與使用者執行緒一起工作,所以總體上來說,CMS收集器的記憶體回收過程是與使用者執行緒一起併發地執行的 優點:併發收集、低停頓 缺點:採用標記-清除演算法會產生大量空間碎片,併發階段會降低吞吐量

3.7 G1收集器

並行與併發 分代收集(仍然保留了分代的概念) 空間整合(整體上屬於“標記-整理”演算法,不會導致空間碎片) 可預測的停頓(比CMS更先進的地方在於能讓使用者明確指定一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒) 使用G1收集器時,Java堆的記憶體佈局與就與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。 工作過程(與CMS相似):
  1. 初始標記(Initial Marking) 標記一下GC Roots能夠關聯的物件,並且修改TAMS的值,需要暫停使用者執行緒
  2. 併發標記(Concurrent Marking) 從GC Roots進行可達性分析,找出存活的物件,與使用者執行緒併發執行
  3. 最終標記(Final Marking) 修正在併發標記階段因為使用者程式的併發執行導致變動的資料,需暫停使用者執行緒
  4. 篩選回收(Live Data Counting and Evacuation) 對各個Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間制定回收計劃

3.8 分類

  • 序列收集器->Serial和Serial Old
只能有一個垃圾回收執行緒執行,使用者執行緒暫停。 適用於記憶體比較小的嵌入式裝置 。
  • 並行收集器[吞吐量優先]->Parallel Scanvenge、Parallel Old
多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態。 適用於科學計算、後臺處理等弱互動場景 。
  • 併發收集器[停頓時間優先]->CMS、G1
使用者執行緒和垃圾收集執行緒同時執行(但並不一定是並行的,可能是交替執行的),垃圾收集執行緒在執行的時候不會停頓使用者執行緒的執行。 適用於相對時間有要求的場景,比如Web 。

3.9 如何選擇合適的垃圾收集器

官網

[https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28)
  • 優先調整堆的大小讓伺服器自己來選擇
  • 如果記憶體小於100M,使用序列收集器
  • 如果是單核,並且沒有停頓時間要求,使用序列或JVM自己選
  • 如果允許停頓時間超過1秒,選擇並行或JVM自己選
  • 如果響應時間最重要,並且不能超過1秒,使用併發收集器
**開啟方式:** (1)序列 -XX:+UseSerialGC -XX:+UseSerialOldGC (2)並行(吞吐量優先): -XX:+UseParallelGC -XX:+UseParallelOldGC (3)併發收集器(響應時間優先) -XX:+UseConcMarkSweepGC -XX:+UseG1GC