關于JVM的垃圾回收
- 1、垃圾回收的幾個基本問題
-
- 1.1、什麼場景下該使用什麼垃圾回收政策?
- 1.2、垃圾回收發生在哪些區域?
- 1.3、對象在什麼時候能夠被回收?
- 2、可達性分析
-
- 2.1、可達性分析說明圖
- 2.2、什麼是根對象
- 2.3、關于幾種引用類型
- 2.4、關于finalize() 方法
- 3、三種基本垃圾回收算法
-
- 3.1、标記-清除(Mark-Sweep)算法
- 3.2、标記-整理(Mark-Compact)算法
- 3.3、複制(Copy)算法
- 3.4、三種基礎垃圾回收算法的對比
- 4、兩種綜合垃圾回收算法
-
- 4.1、分代收集算法
-
- 4.1.1、關于分代收集算法
- 4.1.2、分代回收對象
- 4.1.3、不同區域觸發垃圾回收的條件
- 4.1.4、關于堆記憶體(Heap)的兩點注意項
- 4.1.5、分代收集算法的好處與調優原則
- 4.2、增量算法
1、垃圾回收的幾個基本問題
1.1、什麼場景下該使用什麼垃圾回收政策?
- 在對記憶體要求苛刻的場景:想辦法提高垃圾的回收效率,多回收掉一些對象,騰出更多記憶體。
- 在CPU使用率高的情況下:降低高并發時垃圾回收的頻率,讓CPU更多的去執行你的業務,而不是垃圾回收。
1.2、垃圾回收發生在哪些區域?
- 堆(Heap) 和 方法區線程共享,是垃圾回收需要考慮的區域。
- 虛拟機棧、本地方法棧以及程式計數器都是線程隔離的,随着線程的建立而建立,随着線程的銷毀而銷毀。是以不需要考慮垃圾回收。
1.3、對象在什麼時候能夠被回收?
-
引用計數法(java 未使用該方法):
|_ 通過對象的引用計數器來判斷該對象是否被引用。當對象間循環引用時,引用技術法就不起作用了。
-
可達性分析:
|_ 以根對象(GC Roots)作為起點向下搜尋,走過的路徑被稱為引用鍊(Reference Chain),如果某個對象到根對象沒有引用鍊相連時,就認為這個對象是不可達的,可以回收。
2、可達性分析
2.1、可達性分析說明圖
以下為可達性分析說明圖(圖 2-1):
2.2、什麼是根對象
思考( 圖2-1 )中的根對象(GC Roots)具體包含了哪些對象?
- 虛拟機棧(棧幀中的本地變量表)中引用的對象。
- 方法區中類靜态屬性引用的對象。
- 方法區中常量引用的對象。
- 本地方法棧中JNI(即 Native方法)引用的對象。
需要注意的是:
一個對象即使不可達,也不一定會被回收
。
當對象變成(GC Roots)不可達時,GC會判斷該對象是否覆寫了finalize方法,若未覆寫,則直接将其回收。否則,若對象未執行過finalize方法,将其放入F-Queue隊列,由一低優先級線程執行該隊列中對象的finalize方法。執行finalize方法完畢後,GC會再次判斷該對象是否可達,若不可達,則進行回收,否則,對象“複活”。
見以下說明圖(圖 2-2):
2.3、關于幾種引用類型
-
強引用(StrongReference)
|_
|_形如 Object obj = new Object() 的引用。通過關鍵字new建立的對象所關聯的引用就是強引用。
如果一個對象具有強引用,那垃圾回收器絕不會回收它。當記憶體空間不足,Java虛拟機甯願抛出OutOfMemoryError錯誤,使程式異常終止,也不會靠随意回收具有強引用的對象來解決記憶體不足的問題。
-
軟引用
|_
|_軟引用通過java.lang.SoftReference類實作。形如 SoftReference<String> str = new SoftReference<>("hello")
|_是用來描述一些有用但非必需的對象。
軟引用關聯的對象,隻有在記憶體不足的時候才會回收。
-
弱引用
|_
|_弱引用通過WeakReference類實作。形如 WeakReference<String> str = new WeakReference<>("hello")
|_弱引用也是用來描述非必需對象的。
無論記憶體是否充足,都會回收被弱引用關聯的對象。
-
虛引用
|_
形如 ReferenceQueue<String> queue = new ReferenceQueue<>();
|_ 不影響對象的生命周期,如果一個對象隻有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動,必須和引用隊列(ReferenceQueue)配合使用。當垃圾回收器準備回收一個對象時,如果發現他還有虛引用,就會回收對象的記憶體之前,把這個虛引用加入到與之關聯的引用隊列中。程式可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否将要被垃圾回收。如果程式發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的記憶體被回收之前采取必要的行動。PhantomReference<String> pha = new PhantomReference<>("hello", queue);
2.4、關于finalize() 方法
- finalize()是Object的protected方法,子類可以覆寫該方法以實作資源清理工作,GC在回收對象之前調用該方法。
- 避免使用finalize()方法,操作不當可能會導緻問題。
- finalize() 優先級低,何時會被調用無法确定,因為什麼時間發生 GC不确定。
- 建議使用 try…catch…finally 來替代 finalize()。
3、三種基本垃圾回收算法
3.1、标記-清除(Mark-Sweep)算法
- 标記需要回收的對象。
- 清理掉要回收的對象。
3.2、标記-整理(Mark-Compact)算法
- 标記需要回收的對象。
- 把所有的存活對象壓縮到記憶體的一端。
- 清理掉邊界外的所有空間。
3.3、複制(Copy)算法
- 把記憶體分為兩塊,每次隻使用一塊。
- 将正在使用的記憶體中的存活對象複制到未使用的記憶體中去,然後清除掉正在使用的記憶體中的所有對象。
- 交換兩個記憶體的角色,等待下次回收。
3.4、三種基礎垃圾回收算法的對比
回收算法 | 優點 | 缺點 |
---|---|---|
标記-清除 | 實作簡單 | 存在記憶體碎片、配置設定記憶體速度會受影響 |
标記-整理 | 無碎片 | 整理存在開銷 |
複制 | 性能好、無碎片 | 記憶體使用率低 |
4、兩種綜合垃圾回收算法
4.1、分代收集算法
4.1.1、關于分代收集算法
分代收集算法的說明:
-
把記憶體分成多個區域,不同區域使用不同的回收算法回收對象。
-
各種商業虛拟機堆記憶體的垃圾收集基本上都采用了分代收集。
-
根據對象的存活周期,把記憶體分成多個區域,不同區域使用不同的回收算法回收對象。
4.1.2、分代回收對象
- 新生代回收(Minor GC | Young GC)
- 老年代回收(Major GC)
- 清理整個堆 (Full GC)
- 因為 Major GC 執行時,一般會伴随的執行一次 Minor GC ,是以 Major GC 約等于 Full GC
4.1.3、不同區域觸發垃圾回收的條件
- 當伊甸園空間不足時,觸發新生代(Minor GC)垃圾回收。
- 觸發老年代(Full GC)垃圾回收的條件:
- 老年代(Tenured)空間不足
- 元空間(Metaspace)不足
- 要晉升到老年代(Tenured)的對象,所占用的空間大于老年代的剩餘空間。
- 顯示調用了 System.gc()
- 建議垃圾回收器執行垃圾回收
- -XX:+DisableExplicitGC 參數,忽略掉System.gc()的調用
4.1.4、關于堆記憶體(Heap)的兩點注意項
1、建立的對象不一定配置設定到伊甸園(Eden)
-
對于大于 -XX:PretenureSizeThreshold,就會直接配置設定到老年代。
-
新生代空間不夠。
2、對象不一定要達到年齡才進入老年代
-
。動态年齡:如果幸存區(Survivor)空間中多有相同年齡對象大小的總和,大于 Survivor 空間的一半,那麼年齡大于等于該年齡的對象就直接進入老年代
4.1.5、分代收集算法的好處與調優原則
4.2、增量算法
- 增量算法就是每次隻收集一小片區域的記憶體空間的垃圾
.