🍀 以前收集器特點
📌 年輕代和老年代是各自獨立且連接的內存塊;
📌 年輕代收集使用單 Eden+S0+S1 進行復制算法;
📌 老年代收集必須掃描整個老年代區域;
📌 都是以儘可能少而快速地執行 GC 爲設計原則。
🍀 G1 是什麼
G1(Garbage-First)收集器,是一款面向服務端應用的收集器
從官網的描述中,我們知道G1是一種服務器端的垃圾收集器,應用在多處理器和大容量內存環境中,在實現高吞吐量的同時,儘可能的滿足垃圾收集暫停時間的要求。另外,它還具有以下特性:
像 CMS 收集器一樣,能與應用程序線程併發執行。
整理空閒空間更快。
需要更多的時間來預測 GC 停頓時間。
不希望犧牲大量的吞吐性能。
不需要更大的Java Heap。
G1收集器的設計目標是取代 CMS 收集器,它同 CMS 相比,在以下方面表現的更出色:
G1是一個有整理內存過程的垃圾收集器,不會產生很多內存碎片。
G1的Stop The World(STW) 更可控,G1在停頓時間上添加了預測機制,用戶可以指定期望停頓時間。
CMS垃圾收集器雖然減少了暫停應用程序的運行時間,但是它還是存在着內存碎片問題。於是,爲了去除內存碎片問題,同時又保留 CMS 垃圾收集器低暫停時間的優點,Java7發佈了一個新的垃圾收集器 - G1垃圾收集器。
G1是在2012年纔有jdk1.7u4中可用。Oracle官方計劃在jdk9中將G1變成默認的垃圾收集器以替代 CMS。它是一款面向服務端應用的收集器,主要應用在多CPU和大內存服務器環境下,極大的減少垃圾收集的停頓時間,全面提升服務器的性能,逐步替換java8以前的CMS收集器。
主要改變是 Eden,Survivor 和 Tenured 等內存區域不再是連續的了,而是變成了一個個大小一樣的 region,
每個 region 從 1M 到 32M 不等。一個region有可能屬於Eden,Survivor或者Tenured內存區域。
📌 特點
- 1、G1 能充分利用多 CPU、多核環境硬件優勢,儘量縮短 STW。
- 2、G1 整體上採用標記-整理算法,局部是通過複製算法,不會產生內存碎片。
- 3、宏觀上看G1之中不再區分年輕代和老年代。把內存劃分成多個獨立的子區域(Region),可以近似理解爲一個圍棋的棋盤。
- 4、G1收集器裏面講整個的內存區都混合在一起了,但其本身依然在小範圍內要進行年輕代和老年代的區分,保留了新生代和老年代,但它們不再是物理隔離的,而是一部分Region的集合且不需要Region是連續的,也就是說依然會採用不同的GC方式來處理不同的區域。
- 5、G1雖然也是分代收集器,但整個內存分區不存在物理上的年輕代與老年代的區別,也不需要完全獨立的 survivor(to space) 堆做複製準備。G1只有邏輯上的分代概念,或者說每個分區都可能隨G1的運行在不同代之間前後切換;
🍀 底層原理
📌 Region 區域化垃圾收集器
區域化內存劃片Region,整體編爲了一些列不連續的內存區域,避免了全內存區的GC操作。
核心思想是將整個堆內存區域分成大小相同的子區域(Region),在JVM啓動時會自動設置這些子區域的大小,
在堆的使用上,G1並不要求對象的存儲一定是物理上連續的只要邏輯上連續即可,每個分區的不會固定地爲某個代服務,可以按需在年輕代和老年代之間切換。啓動時可以通過參數 -XX:+G1HeapRegionSize=n 可指定分區大小(1MB~32MB,且必須是2的冪),默認將整堆劃分爲2048個分區。
大小範圍在1MB~32MB,最多能設置2048個區域,也即能夠支持的最大內存爲:32MB * 2048 = 65536MB = 64G內存
G1 將新生代,老年代的物理空間劃分取消了 | G1 算法將堆劃分爲若干區域(Region) |
---|---|
G1算法將堆劃分爲若干個區域(Region),它仍然屬於分代收集器 這些Region的一部分包含新生代,新生代的垃圾收集依然採用暫停所有應用線程的方式,將存活對象拷貝到老年代或者Survivor空間。 這些Region的一部分包含老年代,G1收集器通過將對象從一個區域複製到另外一個區域,完成了清理工作。這就意味着,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有CMS內存碎片問題的存在了。 |
|
在G1中,還有一種特殊的區域,叫Humongous(巨大的)區域 如果一個對象佔用的空間超過了分區容量50%以上,G1收集器就認爲這是一個巨型對象。這些巨型對象默認直接回被分配在年老代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負面影響。爲了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型對象。如果一個H區裝不下一個巨型對象,那麼G1會尋找連續的H分區來存儲。爲了能找到連續的H區,有時候不得不啓動 Full GC。 |
⏳ 最大好處是化整爲零,避免全內存掃描,只需要按照區域來進行掃描即可。
📌 回收步驟
G1 收集器下的 Young GC
針對 Eden 區進行收集,Eden 區耗盡後會被觸發,主要是小區域收集 + 形成連續的內存塊,避免內存碎片
- Eden 區的數據移動到 Survivor 區,假如出現 Survivor 區空間不夠,Eden區數據會部分晉升到 Old 區
- Survivor 區的數據移動到新的 Survivor 區,部分數據晉升到 Old 區
- 最後 Eden 區收拾乾淨了,GC 結束,用戶的應用程序繼續執行。
📌 4步過程
初始標記:只標記GC Roots能直接關聯到的對象
併發標記:進行GC Roots Tracing的過程
最終標記:修正併發標記期間,因程序運行導致標記發生變化的那一部分對象
篩選回收:根據時間來進行價值最大化的回收
形如:
🍀 Case 案例
package com.brian.interview.study.jvm.gc;
import java.util.Random;
/**
* Copyright (c) 2020 ZJU All Rights Reserved
* <p>
* Project: JavaSomeDemo
* Package: com.brian.interview.study.jvm.gc
* Version: 1.0
* <p>
* Created by Brian on 2020/2/16 18:05
*/
/**
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseG1GC
*/
public class G1Demo {
public static void main(String[] args) {
String str = "hello";
while (true) {
str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
str.intern();
}
}
}
🍀 常用配置參數(瞭解)
開發人員僅僅需要聲明以下參數即可:
三步歸納:開始G1+設置最大內存+設置最大停頓時間
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=n:最大GC停頓時間單位毫秒,這是個軟目標,JVM將盡可能(但不保證)停頓小於這個時間
📌 -XX:+UseG1GC
📌 -XX:G1HeapRegionSize=n:設置的G1區域的大小。值是2的冪,範圍是1MB到32MB。目標是根據最小的Java堆大小劃分出約 2048 個區域
📌 -XX:MaxGCPauseMills=n:最大GC停頓時間,這是個軟目標,JVM將盡可能(但不保證)停頓小於這個時間
📌 -XX:InitiatingHeapOccupancyPercent=n:堆佔用了多少的時候就觸發GC,默認爲45
📌 -XX:ConcGCThreads=n:併發GC使用的線程數
📌 -XX:G1ReservePercent=n:設置作爲空閒空間的預留內存百分比,以降低目標空間溢出的風險,默認值是10%
🍀 和 CMS 相比的優勢
比起 CMS 有兩個優勢:
- 1)G1不會產生內存碎片。
- 2)是可以精準控制停頓。該收集器是把整個堆(新生代、老年代)劃分成多個固定大小的區域,每次根據允許停頓的時間去收集垃圾最多的區域。