垃圾收集器以及記憶體分配

一、分類

  在jvm中,實現了多種垃圾收集器,包括:串列垃圾收集器、並行垃圾收集器、CMS(並發)垃圾收集器、G1垃圾收集器

二、串列垃圾收集器

  串列垃圾收集器,是指使用單執行緒進行垃圾回收,垃圾回收時,只有一個執行緒在工作,並且java應用中的所有執行緒都要暫停,等待垃圾回收的完成。這種現象稱之為STW(Stop-The-World)。
  對於交互性較強的應用而言,這種垃圾收集器是不能夠接受的。
  一般在Javaweb應用中是不會採用該收集器的。 

1、編寫測試程式碼

package com.zn;    import java.util.ArrayList;  import java.util.List;  import java.util.Properties;  import java.util.Random;    public class GCTest {      public static void main(String[] args) throws Exception {          List<Object> list = new ArrayList<Object>();          while (true) {              int sleep = new Random().nextInt(100);              if (System.currentTimeMillis() % 2 == 0) {                  list.clear();              } else {                  for (int i = 0; i < 10000; i++) {                      Properties properties = new Properties();                      properties.put("key_" + i, "value_" + System.currentTimeMillis() + i);                      list.add(properties);                  }              }              // System.out.println("list大小為:" + list.size());               Thread.sleep(sleep);          }      }  }

2、設置垃圾回收為串列收集器

  在程式運行參數中添加2個參數,如下:
    -XX:+UseSerialGC:指定年輕代和老年代都使用串列垃圾收集器
    -XX:+PrintGCDetails:列印垃圾回收的詳細資訊
-XX:+UseSerialGC -XX:+PrintGCDetails -Xms16m -Xmx16m

   

3、啟動程式資訊列印

  

GC日誌資訊解讀:
  年輕代的記憶體GC前後的大小:
  DefNew:表示使用的是串列垃圾收集器。
   4928K->512K(4928K):表示,年輕代GC前,佔有4416K記憶體,GC後,佔有512K記憶體,總大小4928K 
  0.0043929 secs:表示,GC所用的時間,單位為毫秒。
  11130K->9711K(15872K):表示,GC前,堆記憶體佔有11130K,GC後,佔有9711K,總大小為15872K
  Full GC:表示,記憶體空間全部進行GC

二、並行垃圾收集器

  並行垃圾收集器在串列垃圾收集器的基礎之上做了改進,將單執行緒改為了多執行緒進行垃圾回收,這樣可以縮短垃圾回收的時間。(這裡是指,並行能力較強的機器)當然了,並行垃圾收集器在收集的過程中也會暫停應用程式,這個和串列垃圾回收器是一樣的,只是並行執行,速度更快些,暫停的時間更短一些。

2.1  ParNew垃圾收集器

  ParNew垃圾收集器是工作在年輕代上的,只是將串列的垃圾收集器改為了並行。
  通過-XX:+UseParNewGC參數設置年輕代使用ParNew回收器,老年代使用的依然是串列收集器。

  1、編寫測試程式碼

package com.zn;    import java.util.ArrayList;  import java.util.List;  import java.util.Properties;  import java.util.Random;    public class GCTest {      public static void main(String[] args) throws Exception {          List<Object> list = new ArrayList<Object>();          while (true) {              int sleep = new Random().nextInt(100);              if (System.currentTimeMillis() % 2 == 0) {                  list.clear();              } else {                  for (int i = 0; i < 10000; i++) {                      Properties properties = new Properties();                      properties.put("key_" + i, "value_" + System.currentTimeMillis() + i);                      list.add(properties);                  }                // System.out.println("list大小為:" + list.size());              Thread.sleep(sleep);          }      }  }

  2、設置垃圾回收為ParNew收集器

-XX:+UseParNewGC -XX:+PrintGCDetails -Xms16m -Xmx16m

  

  3、啟動程式資訊列印

  

  由以上資訊可以看出, ParNew: 使用的是ParNew收集器。其他資訊和串列收集器一致。

2.2  ParallelGC垃圾收集器 

  ParallelGC收集器工作機制和ParNewGC收集器一樣,只是在此基礎之上,新增了兩個和系統吞吐量相關的參數,使得其使用起來更加的靈活和高效。
  相關參數如下:
    -XX:+UseParallelGC:年輕代使用ParallelGC垃圾回收器,老年代使用串列回收器。
    -XX:+UseParallelOldGC:年輕代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。
    -XX:MaxGCPauseMillis:設置最大的垃圾收集時的停頓時間,單位為毫秒
      需要注意的是,ParallelGC為了達到設置的停頓時間,可能會調整堆大小或其他的參數,如果堆的大小設置的較小,就會導致GC工作變得很頻繁,反而可能會影響到性能。
      該參數使用需謹慎。
    -XX:GCTimeRatio:設置垃圾回收時間占程式運行時間的百分比,公式為1/(1+n)。
      它的值為0~100之間的數字,默認值為99,也就是垃圾回收時間不能超過1%
    -XX:UseAdaptiveSizePolicy:自適應GC模式,垃圾回收器將自動調整年輕代、老年代等參數,達到吞吐量、堆大小、停頓時間之間的平衡。
      一般用於,手動調整參數比較困難的場景,讓收集器自動進行調整。

  1、編寫測試程式碼

package com.zn;    import java.util.ArrayList;  import java.util.List;  import java.util.Properties;  import java.util.Random;    public class GCTest {      public static void main(String[] args) throws Exception {          List<Object> list = new ArrayList<Object>();          while (true) {              int sleep = new Random().nextInt(100);              if (System.currentTimeMillis() % 2 == 0) {                  list.clear();              } else {                  for (int i = 0; i < 10000; i++) {                      Properties properties = new Properties();                      properties.put("key_" + i, "value_" + System.currentTimeMillis() + i);                      list.add(properties);                  }                // System.out.println("list大小為:" + list.size());              Thread.sleep(sleep);          }      }  }

  2、設置垃圾回收為ParallelGC收集器

-XX:+UseParallelGC  -XX:+UseParallelOldGC  -XX:MaxGCPauseMillis=100  -XX:+PrintGCDetails  -Xms16m  -Xmx16m

  

  3、啟動程式資訊列印

  

  由以上資訊可以看出,年輕代和老年代都使用了ParallelGC垃圾回收器。

三、CMS垃圾收集器

  CMS全稱 Concurrent Mark Sweep,是一款並發的、使用標記-清除演算法的垃圾回收器,該回收器是針對老年代垃圾回收的,通過參數-XX:+UseConcMarkSweepGC進行設置。
  CMS垃圾回收器的執行過程如下: 

  

  • 初始化標記(CMS-initial-mark) ,標記root,會導致stw;
  • 並發標記(CMS-concurrent-mark),與用戶執行緒同時運行;
  • 預清理(CMS-concurrent-preclean),與用戶執行緒同時運行;
  • 重新標記(CMS-remark) ,會導致stw;
  • 並發清除(CMS-concurrent-sweep),與用戶執行緒同時運行;
  • 調整堆大小,設置CMS在清理之後進行記憶體壓縮,目的是清理記憶體中的碎片;
  • 並發重置狀態等待下次CMS的觸發(CMS-concurrent-reset),與用戶執行緒同時運行; 

1、編寫測試程式碼

package com.zn;    import java.util.ArrayList;  import java.util.List;  import java.util.Properties;  import java.util.Random;    public class GCTest {      public static void main(String[] args) throws Exception {          List<Object> list = new ArrayList<Object>();          while (true) {              int sleep = new Random().nextInt(100);              if (System.currentTimeMillis() % 2 == 0) {                  list.clear();              } else {                  for (int i = 0; i < 10000; i++) {                      Properties properties = new Properties();                      properties.put("key_" + i, "value_" + System.currentTimeMillis() + i);                      list.add(properties);                  }              }              // System.out.println("list大小為:" + list.size());              Thread.sleep(sleep);          }      }  }

2、設置垃圾回收為CMS收集器

-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xms16m -Xmx16m

  

3、啟動程式資訊列印

  CMS執行的過程:

  

四、G1垃圾收集器(重點)

  G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方計劃在jdk9中將G1變成默認的垃圾收集器,以替代CMS。 
  G1的設計原則就是簡化JVM性能調優,開發人員只需要簡單的三步即可完成調優:
    1. 第一步,開啟G1垃圾收集器
    2. 第二步,設置堆的最大記憶體
    3. 第三步,設置最大的停頓時間
  G1中提供了三種模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不同的條件下被觸發。

4.1  原理

  G1垃圾收集器相對比其他收集器而言,最大的區別在於它取消了年輕代、老年代的物理劃分,取而代之的是將堆劃分為若干個區域(Region),這些區域中包含了有邏輯上的年輕代、老年代區域。
  這樣做的好處就是,我們再也不用單獨的空間對每個代進行設置了,不用擔心每個代內存是否足夠。

  

  在G1劃分的區域中,年輕代的垃圾收集依然採用暫停所有應用執行緒的方式,將存活對象拷貝到老年代或者Survivor空間,G1收集器通過將對象從一個區域複製到另外一個區域,完成了清理工作。
  這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms記憶體碎片問題的存在了。
  在G1中,有一種特殊的區域,叫Humongous區域。
    如果一個對象佔用的空間超過了分區容量50%以上,G1收集器就認為這是一個巨型對象。
    這些巨型對象,默認直接會被分配在老年代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負面影響。
    為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型對象。如果一個H區裝不下一個巨型對象,那麼G1會尋找連續的H分區來存儲。為了能找到連續的H區,有時候不得不啟動Full GC。

4.2  Young GC

  Young GC主要是對Eden區進行GC,它在Eden空間耗盡時會被觸發。
    Eden空間的數據移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分數據會直接晉陞到年老代空間。
    Survivor區的數據移動到新的Survivor區中,也有部分數據晉陞到老年代空間中。
    最終Eden空間的數據為空,GC停止工作,應用執行緒繼續執行。
  
  

   Remembered Set(已記憶集合)

  在GC年輕代的對象時,我們如何找到年輕代中對象的根對象呢?
    根對象可能是在年輕代中,也可以在老年代中,那麼老年代中的所有對象都是根么?
  如果全量掃描老年代,那麼這樣掃描下來會耗費大量的時間。
    於是,G1引進了RSet的概念。它的全稱是Remembered Set,其作用是跟蹤指向某個堆內的對象引用。

     

  每個Region初始化時,會初始化一個RSet,該集合用來記錄並跟蹤其它Region指向該Region中對象的引用,每個Region默認按照512Kb劃分成多個Card,所以RSet需要記錄的東西應該是 xx Region的 xx Card。 

4.3  Mixed GC

  當越來越多的對象晉陞到老年代old region時,為了避免堆記憶體被耗盡,虛擬機會觸發一個混合的垃圾收集器,即Mixed GC,該演算法並不是一個Old GC,除了回收整個Young
  Region,還會回收一部分的Old Region,這裡需要注意:是一部分老年代,而不是全部
  老年代,可以選擇哪些old region進行收集,從而可以對垃圾回收的耗時時間進行控制。
  也要注意的是Mixed GC 並不是 Full GC。
  MixedGC什麼時候觸發?
    由參數 -XX:InitiatingHeapOccupancyPercent=n 決定。默認:45%,該參數的意思是:當老年代大小占整個堆大小百分比達到該閥值時觸發。
  它的GC步驟分2步:
    ①全局並發標記(global concurrent marking)
    ② 拷貝存活對象(evacuation)

  1、全局並發標記

  全局並發標記,執行過程分為五個步驟:
    初始標記(initial mark,STW)
      標記從根節點直接可達的對象,這個階段會執行一次年輕代GC,會產生全局停頓。
    根區域掃描(root region scan)
      G1 GC 在初始標記的存活區掃描對老年代的引用,並標記被引用的對象。
      該階段與應用程式(非 STW)同時運行,並且只有完成該階段後,才能開始下
      一次 STW 年輕代垃圾回收。
    並發標記(Concurrent Marking)
      G1 GC 在整個堆中查找可訪問的(存活的)對象。該階段與應用程式同時運行,可以被 STW 年輕代垃圾回收中斷。
    重新標記(Remark,STW)
      該階段是 STW 回收,因為程式在運行,針對上一次的標記進行修正。
    清除垃圾(Cleanup,STW)
      清點和重置標記狀態,該階段會STW,這個階段並不會實際上去做垃圾的收集,等待evacuation階段來回收。 

  2、拷貝存活對象

    Evacuation階段是全暫停的。該階段把一部分Region里的活對象拷貝到另一部分Region中,從而實現垃圾的回收清理。 

4.4  G1收集器相關參數

  -XX:+UseG1GC:使用 G1 垃圾收集器
  -XX:MaxGCPauseMillis:設置期望達到的最大GC停頓時間指標(JVM會儘力實現,但不保證達到),默認值是 200 毫秒。
  -XX:G1HeapRegionSize=n:設置的 G1 區域的大小。值是 2 的冪,範圍是 1 MB 到 32 MB 之間。目標是根據最小的 Java 堆大小劃分出約 2048 個區域。默認是堆記憶體的1/2000。
  -XX:ParallelGCThreads=n:設置 STW 工作執行緒數的值。將 n 的值設置為邏輯處理器的數量。n 的值與邏輯,處理器的數量相同,最多為 8。
  -XX:ConcGCThreads=n:設置並行標記的執行緒數。將 n 設置為並行垃圾回收執行緒數 (ParallelGCThreads)的 1/4 左右。
  -XX:InitiatingHeapOccupancyPercent=n:設置觸發標記周期的 Java 堆佔用率閾值。默認佔用率是整個 Java 堆的 45%。  

4.5  測試

  1、編寫測試程式碼

package com.zn;    import java.util.ArrayList;  import java.util.List;  import java.util.Properties;  import java.util.Random;    public class GCTest {      public static void main(String[] args) throws Exception {          List<Object> list = new ArrayList<Object>();          while (true) {              int sleep = new Random().nextInt(100);              if (System.currentTimeMillis() % 2 == 0) {                  list.clear();              } else {                  for (int i = 0; i < 10000; i++) {                      Properties properties = new Properties();                      properties.put("key_" + i, "value_" + System.currentTimeMillis() + i);                      list.add(properties);                  }                // System.out.println("list大小為:" + list.size());              Thread.sleep(sleep);          }      }  }

  2、設置垃圾回收為G1 垃圾收集器

-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -Xmx256m

  

  3、啟動程式資訊列印

  

4.6  對於G1垃圾收集器優化建議

  年輕代大小
    避免使用 -Xmn 選項或 -XX:NewRatio 等其他相關選項顯式設置年輕代大小。
    固定年輕代的大小會覆蓋暫停時間目標。
  暫停時間目標不要太過嚴苛
    G1 GC 的吞吐量目標是 90% 的應用程式時間和 10%的垃圾回收時間。
    評估 G1 GC 的吞吐量時,暫停時間目標不要太嚴苛。目標太過嚴苛表示您願意承受更多的垃圾回收開銷,而這會直接影響到吞吐量

 

Tags: