第2講 GC回收機制與分代回收策略

第2講 GC回收機制與分代回收策略

拉勾教育:https://kaiwu.lagou.com/course/courseInfo.htm

說到垃圾回收,首先要知道什麼是“垃圾”,垃圾就是沒有用的對象,那麼怎樣判定一個對象是不是垃圾(能不能被回收)?Java 虛擬機中使用一種叫作可達性分析的算法來決定對象是否可以被回收。

一、可達性分析

可達性分析就通過一組名爲”GC Root"的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,最後通過判斷對象的引用鏈是否可達來決定對象是否可以被回收。如圖:

在這裏插入圖片描述
注意:上圖中圓形圖標雖然標記的是對象,但實際上代表的是此對象在內存中的引用。包括 GC Root 也是一組引用而並非對象。

二、什麼對象可作爲GC-Root對象

  • Java 虛擬機棧(局部變量表)中的引用的對象。也就是正在運行的方法中的局部變量所引用的對象
  • 方法區中靜態引用指向的對象。也就是類中的static修飾的變量所引用的對象
  • 方法區中常量引用的對象。
  • 仍處於存活狀態中的線程對象。
  • Native 方法中 JNI 引用的對象。

以上幾種情況除了常量,其它的都在課程中有代碼示例。

三、垃圾回收算法

3.1 標記清除算法

從”GC Roots”集合開始,將內存整個遍歷一次,保留所有可以被 GC Roots 直接或間接引用到的對象,而剩下的對象都當作垃圾對待並回收,過程分爲 標記清除 兩個步驟。

在這裏插入圖片描述

  • 優點:實現簡單,不需要將對象進行移動。
  • 缺點:這個算法需要中斷進程內其他組件的執行(stop the world),並且可能產生內存碎片,提高了垃圾回收的頻率。

3.2 複製算法

將現有的內存空間分爲兩快,每次只使用其中一塊,在垃圾回收時將正在使用的內存中的存活對象複製到未被使用的內存塊中。之後,清除正在使用的內存塊中的所有對象,交換兩個內存的角色,完成垃圾回收。

在這裏插入圖片描述
在這裏插入圖片描述

  • 優點:按順序分配內存即可,實現簡單、運行高效,不用考慮內存碎片。
  • 缺點:可用的內存大小縮小爲原來的一半,對象存活率高時會頻繁進行復制。

3.3 標記-整理算法

需要先從根節點開始對所有可達對象做一次標記,之後,它並不簡單地清理未標記的對象,而是將所有的存活對象壓縮到內存的一端。最後,清理邊界外所有的空間。

在這裏插入圖片描述

  • 優點:按順序分配內存即可,實現簡單、運行高效,不用考慮內存碎片。
  • 缺點:可用的內存大小縮小爲原來的一半,對象存活率高時會頻繁進行復制。

四、新生代與年老代

分代回收的中心思想就是:對於新創建的對象會在新生代中分配內存,此區域的對象生命週期一般較短。如果經過多次回收仍然存活下來,則將它們轉移到老年代中。

新生成的對象優先存放在新生代中,新生代對象朝生夕死,存活率很低,在新生代中,常規應用進行一次垃圾收集一般可以回收70%~95%的空間,回收效率很高。新生代中因爲要進行一些複製操作,所以一般採用的GC回收算法是複製算法

新生代又可以繼續細分爲3部分:Eden、Survivor0(簡稱S0)、Survivor1(簡稱S1)。這 3 部分按照 8:1:1 的比例來劃分新生代。

絕大多數剛剛被創建的對象會存放在 Eden區

在這裏插入圖片描述
當 Eden 區第一次滿的時候,會進行垃圾回收。首先將 Eden 區的垃圾對象回收清除,並將存活的對象複製到 S0,此時 S1 是空的。

在這裏插入圖片描述

下一次 Eden 區滿時,再執行一次垃圾回收。此次會將 Eden 和 S0 區中所有垃圾對象清除,並將存活對象複製到 S1,此時 S0 變爲空。

在這裏插入圖片描述

如此反覆在 S0 和 S1之間切換幾次(默認 15 次)之後,如果還有存活對象。說明這些對象的生命週期較長,則將它們轉移到老年代中。

在這裏插入圖片描述

一個對象如果在新生代存活了足夠長的時間而沒有被清理掉,則會被複制到老年代。老年代的內存大小一般比新生代大,能存放更多的對象。

如果對象比較大(比如長字符串或者大數組),並且新生代的剩餘空間不足,則這個大對象會直接被分配到老年代上。

我們可以使用-XX:PretenureSizeThreshold來控制直接升入老年代的對象大小,大於這個值的對象會直接分配在老年代上。老年代因爲對象的生命週期較長,不需要過多的複製操作,所以一般採用標記整理回收算法。

五、看懂GC日誌

5.1 參數介紹

-XX:+PrintGC 輸出簡要GC日誌

-XX:+PrintGCDetails 輸出詳細GC日誌

-Xloggc:gc.log 輸出GC日誌到文件

-XX:+PrintGCTimeStamps輸出GC的時間戳(以JVM啓動到當期的總時長的時間戳形式)

-XX:+PrintGCDateStamps輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)

-XX:+PrintHeapAtGC在進行GC的前後打印出堆的信息

-verbose:gc 在JDK 8中,-verbose:gc是-XX:+PrintGC一個別稱,日誌格式等價與:-XX:+PrintGC。不過在JDK 9中 -XX:+PrintGC被標記爲deprecated。
-verbose:gc是一個標準的選項, -XX:+PrintGC是一個實驗的選項,建議使用-verbose:gc替代-XX:+PrintGC

-XX:+PrintReferenceGC 打印年輕代各個引用的數量以及時長

5.2 日誌含義

一、參數:-XX:+PrintGCDetails

日誌內容:

[GC (Allocation Failure) [PSYoungGen: 53248K->2176K(59392K)] 58161K->7161K(256000K), 0.0039189 secs] [Times: user=0.02 sys=0.01, real=0.00 secs]

1、GC 表示是一次YGC(Young GC)

2、Allocation Failure 表示是失敗的類型

3、PSYoungGen 表示年輕代大小

4、53248K->2176K 表示年輕代佔用從53248K降爲2176K

5、59392K表示年輕代的大小

6、58161K->7161K 表示整個堆佔用從53248K降爲2176K

7、256000K表示整個堆的大小

8、 0.0039189 secs 表示這次GC總計所用的時間

9、[Times: user=0.02 sys=0.01, real=0.00 secs] 分別表示,用戶態佔用時長,內核用時,真實用時。

二、參數:-verbose:gc -XX:+PrintHeapAtGC -Xmn64M -Xms256M -Xmx256M

日誌內容:

{Heap before GC invocations=1 (full 0):
 PSYoungGen      total 57344K, used 49152K [0x00000000fc000000, 0x0000000100000000, 0x0000000100000000)
  eden space 49152K, 100% used [0x00000000fc000000,0x00000000ff000000,0x00000000ff000000)
  from space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
  to   space 8192K, 0% used [0x00000000ff000000,0x00000000ff000000,0x00000000ff800000)
 ParOldGen       total 196608K, used 0K [0x00000000f0000000, 0x00000000fc000000, 0x00000000fc000000)
  object space 196608K, 0% used [0x00000000f0000000,0x00000000f0000000,0x00000000fc000000)
 Metaspace       used 7901K, capacity 8264K, committed 8448K, reserved 1056768K
  class space    used 888K, capacity 986K, committed 1024K, reserved 1048576K
[GC (Allocation Failure)  49152K->2416K(253952K), 0.0030218 secs]
Heap after GC invocations=1 (full 0):
 PSYoungGen      total 57344K, used 2400K [0x00000000fc000000, 0x0000000100000000, 0x0000000100000000)
  eden space 49152K, 0% used [0x00000000fc000000,0x00000000fc000000,0x00000000ff000000)
  from space 8192K, 29% used [0x00000000ff000000,0x00000000ff258020,0x00000000ff800000)
  to   space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
 ParOldGen       total 196608K, used 16K [0x00000000f0000000, 0x00000000fc000000, 0x00000000fc000000)
  object space 196608K, 0% used [0x00000000f0000000,0x00000000f0004000,0x00000000fc000000)
 Metaspace       used 7901K, capacity 8264K, committed 8448K, reserved 1056768K
  class space    used 888K, capacity 986K, committed 1024K, reserved 1048576K
}

可以看到執行GC前後的堆佔用情況

[GC (Allocation Failure)  49152K->2416K(253952K), 0.0030218 secs]

invocations 表示GC的次數,每次GC增加一次,每次GC前後的invocations相等

1、Heap before GC invocations=1 表示是第1次GC調用之前的堆內存狀況

2、{Heap before GC invocations=1 (full 0): 表示是第1次GC調用之後的堆內存狀況

其它字段表示意義如下:

在這裏插入圖片描述

六、引用分類與回收時機

在這裏插入圖片描述

需要注意的是,被軟引用對象關聯的對象會自動被垃圾回收器回收,但是軟引用對象本身也是一個對象,這些創建的軟引用並不會自動被垃圾回收器回收掉。

參考 https://mp.weixin.qq.com/s/XRCq3IDdGJt3Nq9Mu23U5g




由於水平有限,如果文中存在錯誤之處,請大家批評指正,歡迎大家一起來分享、探討!

博客:http://blog.csdn.net/MingHuang2017

GitHub:https://github.com/MingHuang1024

Email: [email protected]

一起來分享、探討!

博客:http://blog.csdn.net/MingHuang2017

GitHub:https://github.com/MingHuang1024

Email: [email protected]

微信:724360018

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章