1. 程式人生 > >垃圾回收與算法

垃圾回收與算法

emd full gc 循環 必須 剩余空間 搜索 linear 1.2 hot

前邊講到 JVM 運行時內存的地方,關於新生代、老年代中 GC 垃圾回收以及垃圾回收算法,不知是否有點懵懵的,這篇一起了解一下垃圾回收以及垃圾回收算法。

一、垃圾回收需要完成的三件事

  1. 哪些內存需要回收?——垃圾對象(如何判定對象為垃圾對象)
  2. 如何回收?——垃圾回收算法
  3. 何時回收?

1. 哪些內存需要回收?

判定為 "死" 對象,或者無用對象時即視為可回收內存。

如何判定為垃圾對象,在這有兩個方法。

1.1 引用計數算法

在 Java 中,引用和對象是有關聯的。如果要操作對象則必須用引用進行。因此,很顯然一個簡單的辦法是通過引用計數來判斷一個對象是否可以回收。

簡單說,即一個對象如果沒有任何與之關聯的引用,即他們的引用計數都不為 0,則說明對象不太可能再被用到,那麽這個對象就是可回收對象。

這種方法的效率非常的高,但是卻有一個很大的缺點,無法解決相互引用的對象,導致進入計數的死循環,致使引用計數算法無法通知 GC 收集器回收他們。

因為這種弊端,在主流的 Java 虛擬機裏面沒有選用計數算法來管理內存的。

1.2 可達性分析算法

為了解決引用計數法的循環引用問題,Java 使用了可達性分析的方法。通過一系列的“GC roots”對象作為起點搜索。如果在“GC roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的。

要註意的是,不可達對象不等價於可回收對象,不可達對象變為可回收對象至少要經過兩次標記過程。兩次標記後仍然是可回收對象,則將面臨回收。


2. 如何回收?——垃圾回收算法

2.1 標記-清除算法(幾乎都不用)

該算法為最基礎的垃圾收集算法,如同名稱一樣,該算法分為 "標記" 和 "清除" 兩個階段。

標記階段標記出所有需要回收的對象,清除階段回收被標記的對象所占用的空間。如圖

技術分享圖片

從圖中我們就可以發現,該算法最大的問題是內存碎片化嚴重,空間碎片太多,內存不足時,很容易觸發下一次 GC。

2.2 復制算法(新生代使用)

為了解決 Mark-Sweep 算法內存碎片化的缺陷而被提出的算法。按內存容量將內存劃分為等大小的兩塊。每次只使用其中一塊,當這一塊用完了,就將還存活的對象復制到另一塊上面,然後再把已使用的內存空間一次性清理掉,每次都對整個半區進行內存回收,不需要考慮碎片問題,如圖:

技術分享圖片

這種算法雖然實現簡單,內存效率高,不易產生碎片,但是最大的問題是可用內存被壓縮到了原本的一半。且存活對象增多的話,Copying 算法的效率會大大降低。

補充:現在商業虛擬機都是采用復制算法來回收新生代,對象在創建時,虛擬機將內存劃分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和一塊 Survivor , 當回收時,將 eden 和 Survivor 中還存活著的對象一次性的復制到另一塊 Survivor 空間上,然後清理掉 eden 和剛才用過的 Survivor 空間。

hotspot 默認 Eden 和 Survivor 比例為:8:1,也就是只有 10% 的空間被浪費。

2.3、標記整理算法(老年代使用)

標記整理,跟 標記-清除 算法中的標記過程是一樣的,只是後續不是直接對可回收對象進行清理,而是讓所有存活的對象向一端移動,然後直接清理掉端界邊界以外的內存。如圖:

技術分享圖片

2.4、分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根據對象存活的不同生命周期將內存劃分為不同的域,一般情況下將 GC 堆劃分為老生代(Tenured/Old Generation)和新生代(Young Generation)。

老生代的特點是每次垃圾回收時只有少量對象需要被回收,新生代的特點是每次垃圾回收時都有大量垃圾需要被回收,因此可以根據不同區域選擇不同的算法。


3. 何時回收?

何時回收之前,先了解一下幾種 GC。

  • Minor GC:該 GC 會清理年輕代的內存,GC 時會觸發 "全世界的暫停"
  • Major GC/Full GC:清理老年代。

如下是幾種 GC 的觸發場景:

  1. 執行 system.gc() 的時候
  2. 老年代空間不足,GC 之後,空間不足會觸發 outofmemoryError
  3. 永久代空間不足,GC 之後,空間不足會觸發 java.outofMemory PerGen Space
  4. Minor GC之後 Survior放不下,放入老年代,老年代也放不下,觸發FullGC,或者新生代有對象放入老年代,老年代放不下,觸發FullGC
  5. 新生代晉升為老年代時候,老年代剩余空間低於新生代晉升為老年代的速率,會觸發老年代回收
  6. new 一個大對象,新生代放不下,直接到老年代,空間不夠,觸發FullGC

二、重新疏導 JVM GC 過程。

Java 堆是垃圾收集器管理的主要區域,該區域又分為新生代、老年代,而新生代又分為一個 Eden 區和兩個 Survivor 區,關於對象的創建,優先在 Eden 區中分配,當 Eden 區沒有足夠空間時,虛擬機將觸發一次 Minor GC,由於大多數對象都是朝生夕滅,所以 Minor GC 非常頻繁,速度也非常快。

Full GC,發生在老年代的 GC,當老年代沒有足夠的空間時即發生 Full GC,發生 Full GC 一般都會有一次 Minor GC,我們在這將 Full GC 等價於 Major GC,也許會有疑問,Full GC 跟 Major GC 到底有什麽區別呀,由於許多 Major GC 是由 Full GC 觸發的,所以很多情況下將這兩種 GC 分離是不太可能的,在這就不繼續填坑了,感興趣的小夥伴可以自行搜索了解。

三、最後總結

首先要清楚垃圾回收要幹的三件事,哪些需要回收、如何回收以及何時回收。
通過 "引用計數算法"(不推薦) 或 "可達性分析算法" 來判定需要回收的對象,知道了需要哪些是需要回收的對象,再通過垃圾回收算法(復制、標記清除、標記整理、分代收集)實現垃圾回收。

補一張圖:

技術分享圖片

如果文章有錯的地方歡迎指正,大家互相留言交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關註微信公眾號:niceyoo

技術分享圖片

垃圾回收與算法