1. 程式人生 > 實用技巧 >《深入理解 JVM》

《深入理解 JVM》

深入理解 JVM

(4 條訊息) 深入理解 JVM 一記憶體_張碩的部落格 - CSDN 部落格_jacvajvm 記憶體結構總結

最近發現有些架構師竟然不懂 JVM,我表示很吃驚,難道他工程師階段打醬油了?那是不是說,直接去學習架構就行了 ?

接下來的一個禮拜,會把 JVM 核心內容做一個詳細的總結。

JVM 記憶體

其中 Heap、Method Area 是允許執行緒間共享訪問的區域,其餘部分只在獨立執行緒獨立分配。
在我們常用的 jdk 中使用了 hotspotJVM,HotSpotJVM 中對 Native Method Stack、VM Stack 沒有區分,合併在一起,統稱為 Stack 理解即可。

program counter register

程式計數器是用來指示當前執行緒所執行的位元組碼的行號,JVM 直譯器就是通過使用這個計數器來選取下一條位元組碼指令。對於多執行緒,每個執行緒的執行實際上是獲取 cpu 時間片(多核處理器使用每個核心並行執行一條指令),通過這個計數器保證下一次執行緒執行時,可以恢復到上次記錄的位置繼續執行,因此每個執行緒都有一個獨立的程式計數器。

JIT vs byteCode 直譯器:

https://www.ibm.com/developerworks/cn/java/j-lo-just-in-time/

Stack(VM Stack+Native Method Stack)

Java 方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀(Stack Frame)用於儲存區域性變量表、 運算元棧、 動態連結、 方法出口等資訊。 每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中入棧到出棧的過程。也就是說,一個方法的執行開始與結束,對應這個這個棧幀的入棧與出棧。

區域性變量表存放了編譯期可知的各種基本資料型別(boolean、 byte、 char、 short、 int、float、 long、 double)、物件引用(reference 型別,它不等同於物件本身,可能是一個指向物件起始地址的引用指標,也可能是指向一個代表物件的控制代碼或其他與此物件相關的位置)和

returnAddress 型別(指向了一條位元組碼指令的地址)

Heap

所有物件、陣列都在堆中建立(jdk7 + 該描述並不絕對)。
物件的結構:


物件頭 Mark Word:用於儲存物件自身的執行時資料,如雜湊碼(HashCode)、 GC 分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒 ID、偏向時間戳等, 型別指標, 陣列長度
物件例項資料:物件真正儲存的有效資訊,引用型別、基本型別等等
對齊填充:因為 java 中資料型別必須是 byte 的整數倍, 通過 “對齊填充” 來保證。也就時這部分作為佔位符的作用。
物件的建立:
1. 確保常量池中存放的是已解釋的類,如果沒有則載入這個類型別
2. 確保物件所屬型別已經經過初始化階段
3. 分配記憶體:TLAB(Thread Local Allocation Buffer,TLAB) 或者通過 CAS 鎖直接在 eden 中分配物件
4. 如果需要,則為物件初始化零值否則 置初始值即可。
5. 設定 物件頭 Header
6. 如果有引用,將該物件引用放入棧中。
7. 物件按照程式設計師的意願進行初始化(構造器)

可以看到 3,6,7,物件初始化記憶體後會接著分配引用,這時雖然拿到引用,但是物件初始化未完成。這個也就是 double check(未使用 volatile)會出現問題的原因。

在記憶體分配有兩種方式:
1. 直接移動指標劃分需要的記憶體 (通過 CAS 方式保證其他執行緒不會並行的使用該記憶體);
2 預先對每個執行緒劃分一小塊可使用的記憶體,這個執行緒中的物件初始化時則直接使用這一小塊記憶體,直到預先分配的記憶體使用完,再去使用 CAS 鎖去 Heap 中分配新的記憶體 (-XX:+/-UseTLAB 設定)。

Heap 是 GC 主要的管理區域,從 GC 的角度來看,Heap 被細分為:youngGen、survivor0Gen、survivor1Gen、oldGen,GC 劃分的目的是為了更好地回收管理記憶體。

從記憶體分配的角度來看:Heap 可能被劃分為多個執行緒私有的分配緩衝區域(Thread Local Allocation Buffer,TLAB)。TLAB 劃分的目的是為了更快的分配記憶體。

Method Area

也被稱為 Non-Heap,用來區分 Heap, 儘管他們都是執行緒間共享的區域。主要存放 JVM 載入的 class 資訊,如類名、類結構等類資訊, 訪問修飾符、 常量池、 欄位描述、 方法描述等 JIT 編譯後的程式碼等等。

因此對於頻繁使用 cglib 等動態代理,會產生大量..$class,可能會導致該區域的 OOM(OOM:Pengen), 該區域在 HotSpot 等價於 Pengen.
(http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/)
(http://stackoverflow.com/questions/19340013/difference-between-class-area-and-heap)

Method Area—Runtime Constant Pool

Runtime Constant Pool 作為 Method Area 的一部分,用於存放編譯器生成的各種字面量以及符號引用(字面量和符號引用。字面量如文字字串,java 中宣告為 final 的常量值等等,而符號引用如類和介面的全侷限定名,欄位的名稱和描述符,方法的名稱和描述符。),但是並不僅限於編譯期,如 String.intern() 方法就可執行時載入到 RuntimeConstantPool 中。

舉例來說:

public static void main(String[] args) {
        String s="cava";
            String str2 = new StringBuilder("ca").append("va").toString();
            System.out.println(str2.intern() == s);
            System.out.println(str2.intern() == new StringBuilder("ca").append("va").toString());

            System.out.println(str2.intern() == "cav"+"a");
    }

String 物件呼叫 intern 會直接從 Constant Pool 中返回這個字元,如果常量池中沒有,則會直接將這個 String 物件複製到常量池中(JDK6), 或者直接將這個String 的引用儲存在常量池中(JDK7)。

而對於直接使用這個字串,比如 String s=”cava”;,這個字串會直接從常量池中返回, 如果這個常量池沒有 (首次出現),則會把這個常量的引用放入常量池。
注意,對於 HotSpot 虛擬機器,根據官方釋出的路線圖信
息,現在也有放棄永久代並逐步改為採用 Native Memory 來實現方法區的規劃了,在目前已
經發布的 JDK 1.7 的 HotSpot 中,已經把原本放在永久代的字串常量池移出。使用 for loop 死迴圈呼叫 String.intern() 並不能導致 Pergen 溢位。

Direct Memory

並不屬於 JVM 記憶體規範中的部分,但是這部分記憶體也被頻繁地使用,而且也可能導致 OutOfMemoryError 異常出現。NIO 中會使用 DirectByteBuffer 對 DirectMemory 進行操作。

/**
*VM Args:-Xmx20M-XX:MaxDirectMemorySize=10M
*@author zzm
*/
public class DirectMemoryOOM{
private static final int_1MB=1024*1024;
public static void main(String[]args)throws Exception{
Field unsafeField=Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe=(Unsafe)unsafeField.get(null);
while(true){
unsafe.allocateMemory(_1MB);
}}}
Exception in thread"main"java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at org.fenixsoft.oom.DMOOM.main(DMOOM.java:20)

由 DirectMemory 導致的記憶體溢位,一個明顯的特徵是在 Heap Dump 檔案中不會看見明顯
的異常,如果讀者發現 OOM 之後 Dump 檔案很小,而程式中又直接或間接使用了 NIO,那就
可以考慮檢查一下是不是這方面的原因。

參考資料:

深入理解 JVM

http://java.jr-jr.com/2015/12/02/java-object-size/


深入理解 JVM 一 GC(上)

(4 條訊息) 深入理解 JVM 一 GC(上)_張碩的部落格 - CSDN 部落格_深入理解 jvm & g1 gc

文章目錄

對於 GC 我們首先會思考的問題是:
1. 哪些記憶體要回收?哪些不用?
2. 如何回收?演算法
3. 何時回收?觸發

GC 回收哪些記憶體?

在上一篇文章中,詳細說了 jvm 記憶體的模型。
深入理解 JVM 一記憶體

因為 program counter register、stack(native method stack、VM stack)是隨著 jvm 中執行緒的產生而產生,執行緒的湮滅而消失。這個幾個區域,基本是在執行之前就已確定的,所以 gc 不作用於這部分記憶體。
gc 主要作用於 Heap、Method Area ,這些區域基本都是執行時才可知,才建立的。這部分記憶體的分配、回收都是動態的。

GC 如何回收?

很多人會說 GC 回收使用引用計數器演算法:就是用一個計數器判斷物件是否被引用,被引用一次,計數器 + 1,釋放引用 - 1。當計數器為 0,則回收這個物件。但是主流 gc 中都沒有使用這個演算法,主要是因為這個演算法無法解決類之間相互引用的問題。比如:

public class ReferenceCountingGC {
    public Object instance = null;

    private static final int _1MB = 1024 * 1024;

    private byte[] bigSize = new byte[2 * _1MB];// 佔用空間

    public static void testGC() {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();

        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        // 假設發生GC, objA、objB是否被回收。objA、objB相互引用中。
        System.gc();

    }

    public static void main(String[] args) {
        testGC();
    }
//[GC [PSYoungGen: 6717K->680K(76288K)] 6717K->680K(249856K), 0.0023914 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
//[Full GC [PSYoungGen: 680K->0K(76288K)] [ParOldGen: 0K->587K(173568K)] 680K->587K(249856K) [PSPermGen: 2569K->2568K(21504K)], 0.0316210 secs] [Times: user=0.09 sys=0.00, real=0.03 secs] 
//                Heap
//                 PSYoungGen      total 76288K, used 1966K [0x00000007ab300000, 0x00000007b0800000, 0x0000000800000000)
//                  eden space 65536K, 3% used [0x00000007ab300000,0x00000007ab4eb920,0x00000007af300000)
//                  from space 10752K, 0% used [0x00000007af300000,0x00000007af300000,0x00000007afd80000)
//                  to   space 10752K, 0% used [0x00000007afd80000,0x00000007afd80000,0x00000007b0800000)
//                 ParOldGen       total 173568K, used 587K [0x0000000701a00000, 0x000000070c380000, 0x00000007ab300000)
//                  object space 173568K, 0% used [0x0000000701a00000,0x0000000701a92d40,0x000000070c380000)
//                 PSPermGen       total 21504K, used 2575K [0x00000006fc800000, 0x00000006fdd00000, 0x0000000701a00000)
//                  object space 21504K, 11% used [0x00000006fc800000,0x00000006fca83ca8,0x00000006fdd00000)

對比刪除物件例項執行後的 GC 日誌,可以確認,上邊的程式碼發生了 GC,兩個相互引用的物件,(很明顯引用計數器不會為 0),也被回收了。可以看出 jvm 並非使用簡單的引用計數器回收演算法。

實際上 hotspotJVM 使用了可達性分析演算法, 通過一些稱為 GC Roots 的物件作為起點,向下掃描,檢視是否有個一條路徑(稱為引用鏈)到達某個物件,如果可以到達,則這個物件依舊存活(可達)。否則清除不可達的物件。

可以作為 GC Roots 物件有哪些?
GC Roots 最為可達性搜尋的起點,首先保證這些物件是要可達的。
static 全域性變數、常量;
棧幀中的本地列表;
(JNI)native 方法引用的物件。

http://it.deepinmind.com/gc/2014/05/13/debugging-to-understand-finalizer.html

再談引用

簡單來說,什麼是引用:
一個 reference 型別的資料儲存的是另一個物件記憶體地址,那麼我們就稱這個 reference 是這個物件的引用。
JDK2 之後,引用型別:
** 強引用:** 例如:String strongReference = new String(),這個 strongReference 就是個強引用,只要這個強引用還存在,還指向這個物件,那麼這個物件就不會被 GC 回收。
通常我們的引用都是強引用。
軟引用:描述有用,但是非必須的物件。在記憶體將要溢位時,會對所有的軟引用做二次回收,如果依舊沒有足夠記憶體,丟擲 OOM。

SoftReference<String> softR = new SoftReference<String>(new String("soft Reference"));

弱引用:更弱的引用,在下一次 GC 時被回收掉。

WeakReference<String> weakReference = new WeakReference(new String("Weak reference"));

虛引用:對被指向的物件不構成任何影響,也無法通過虛引用獲取物件。只是在虛引用指向的物件被回收時,系統會收到一個通知。PhantomReference

finalize() 與 FinalizerQueue

GC in Pergen

對於方法區的 GC,主要說兩點:
1. 常量池中廢棄的常量被回收;
2. 無用的 class 物件被解除安裝。對於大量使用反射、動態代理、cglib、bytecode 等技術的框架中,會頻繁需要解除安裝無用的類。

GC 演算法

Mark-Sweep(標記清除演算法):就是用可達性分析先標記可回收的記憶體,然後清除掉這些被標記記憶體的佔用。標記——> 清除

缺陷:
1. 效率低下,每次回收都要把整個記憶體空間從 roots 分析一遍;
2. 產生記憶體碎片,如果大物件呼叫記憶體,無法獲取連續的空間,會觸發 full-GC;而且要維護一份 free-list,無法通過直接移動指標分配記憶體。

**Copying **(複製演算法):將整個記憶體分為兩塊: A,B,JVM 只在 A 上申請記憶體,當 A 需要清理時,清理 A, 然後把活下來的物件整齊的複製到 B 中,徹底清空 A。

相比於 Mark-Sweep 演算法,優勢是保證了記憶體的連續性(A 中每次都會清空,B 中是整齊連續的)。另外適用於 “物件朝生夕死” 的新生代,只需要複製少量的存活物件,然後徹底清空,效率高。

缺點:
1. 只能使用總記憶體的一部分
2. 需要記憶體擔保(Handler Promotion)
(如上例子中,A 可以使用,B 不能被直接使用),當程式中有過多大物件的使用,比如 A 總共大小為 5M,現在有個物件 10M 怎麼夠用?這就需要 “擔保記憶體”,當自身不夠時,可以直接使用 “擔保”(Handle Promotion)去分配記憶體。

另外,如果 A:B=9:1,那麼可以大大提高使用的記憶體,但是如果清楚 A 時意外發現 100% 的物件都存活,需要複製到 B 中,明顯 B 記憶體不夠, 即使夠用,複製 100% 的物件效率也是低下的。這時也同樣需要 “擔保記憶體”。

其實,在 hotspotJVM 中,A 就是 eden+S0,B 是 S1, 擔保記憶體是 oldGen。當需要將 A 中存活的物件複製到 B 上但 B 不夠的時候,會直接使用 oldGen(擔保) 去。

mark-sweep-compact(標記整理演算法):
標記已經不存活的物件,然後讓所有存活的物件移動到記憶體的一端,然後清理掉不存活的物件空間。標記——>移動(整理)——> 清除。

解決了記憶體空間不連續的問題。

分代收集:
就是針對記憶體的不同區域,使用不同的 GC 演算法。比如,youngGen 使用 copying 演算法;oldGen 使用 mark-sweep 或者 mark-sweep-compact.


連線的收集器都可以組合使用。

Serial 收集器
-XX:+UseSerialGC
最基本的的收集器,單執行緒,同時收集時會暫停所有其他執行緒的執行(stop-the-world)。用於 youngGen。客戶端預設使用。

youngGen 中的 Serial 使用了 copying 收集演算法。

“Serial” is a stop-the-world, copying collector which uses a single GC thread.

Parallel New 收集器(-XX:+UseParNewGC、-XX:+UseConcMarkSweepGC 預設的 youngGen 收集器)
僅適用於新生代。
serial 的多執行緒版本,使用的 gc 演算法與 serial 一致。但是還是會有 stop-the-world。

UseParNewGC is “ParNew” + “Serial Old”
UseConcMarkSweepGC is “ParNew” + “CMS” + “Serial Old”. “CMS” is used most of the time to collect the tenured generation. “Serial Old” is used when a concurrent mode failure occurs.

Parallel Scavenge 吞吐量收集器(-XX:UseParallelGC)

“Parallel Scavenge” is a stop-the-world, copying collector which uses multiple GC threads.
parallel scavenge 類似 parallel New, 同樣是作用於年輕代,使用 copying 回收演算法。但它更關注程式的吞吐量。

吞吐量 vs gc 停頓
http://ifeve.com/useful-jvm-flags-part-6-throughput-collector/
JVM 在專門的執行緒 (GC threads) 中執行 GC。 只要 GC 執行緒是活動的,它們將與應用程式執行緒 (application threads) 爭用當前可用 CPU 的時鐘週期。 簡單點來說,吞吐量是指應用程式執行緒用時佔程式總用時的比例。例如,吞吐量 99/100 意味著 100 秒的程式執行時間應用程式執行緒運行了 99 秒, 而在這一時間段內 GC 執行緒只運行了 1 秒。

然而吞吐量的提高總會帶來暫停時間的增長:比如我想提高系統吞吐量,那麼我就不能讓 GC 執行太過頻繁,才能減少上下文切換; 但是如果 GC 不夠頻繁,那麼單次 GC 執行的時間肯定會增長。
所以,我們要根據實際情況來找準調優目標,如果是後臺運算,我們追求吞吐量,讓計算能力更強。如果是影象化介面,與使用者互動,我們則要縮短暫停時間,以減少卡頓提高體驗。

幾個調優引數:
-XX:MaxGCPauseMillis=(以毫秒為單位)。 通過設定這個值,讓 gc 儘可能的保證 gc 最大的 停頓不要超過這個值,非一定。

通過 - XX:GCTimeRatio = 我們告訴 JVM 吞吐量要達到的目標值。 更準確地說,-XX:GCTimeRatio=N 指定目標應用程式執行緒的執行時間 (與總的程式執行時間) 達到 N/(N+1)的目標比值。 例如,通過 - XX:GCTimeRatio=8 我們要求應用程式執行緒在整個執行時間中至少 8/9 是活動的(因此,GC 執行緒佔用其餘 1/9)。 基於執行時的測量,JVM 將會嘗試修改堆和 GC 設定以期達到目標吞吐量。 -XX:GCTimeRatio 的預設值是 99,也就是說,應用程式執行緒應該執行至少 99% 的總執行時間。
如果 - XX:GCTimeRatio = 與 - XX:MaxGCPauseMillis = 同時使用,優先達到停頓時間目標。

Serial Old 收集器
Serial 收集器的老年代版本

“Serial Old” is a stop-the-world, mark-sweep-compact collector that uses a single GC thread.

Parallel Old 收集器(-XX:+UseParallelOldGC)
parallel scavenge 的老年代版本,多執行緒的標記整理演算法。
通常在注重吞吐量,以及 cpu 敏感的場合使用 Parallel Scavenge
+Parallel Old 的組合!

CMS(concurrent mark sweep)收集器
–XX:+UseConcMarkSweepGC
cms 收集器是一種以獲取最短 GC Pause 為目標的收集器。非常適合重視響應時間的 B/S 系統中。特點是:併發收集(基本與使用者執行緒同時進行)、短停頓。
主要有四個步驟:
1.CMS initial mark 初始標記:
標記 GC Roots 能直接關聯到的物件,Stop-the-world 方式。
2.CMS concurrent mark 併發標記:
這裡的 "併發" 指,GC 執行緒與使用者執行緒同時並行執行。concurrent mark 是不使用 stop-the-world 的方式,進行可達性分析,並標記。
3.CMS remark 重新標記:
因為併發標記過程中,使用者執行緒也在執行,存在引用變更,也可能會產生新的垃圾,所以需要對誤差重新標記。
4.CMS concurrent Sweep 併發清除。
Concurrent Sweep,不使用 stop-the-world 方式,與使用者執行緒同時執行,執行清理工作,會產生記憶體碎片。
注意:第四步中,把清理出來的空間地址放入 free list,以便後續使用。另外,存活的物件沒有移動。

由於 2,4 兩步佔整個 CMS 的絕大部分時間,所以,我們認為 cms 的 gc 是與使用者執行緒同時執行的,不存在 stop-the-world pause。

缺點:
1. 對 cpu 資源敏感。它雖然不會導致使用者執行緒卡頓,但是因為在併發標記、併發清理時 (與使用者執行緒同時) 佔用 cpu 資源(或者說佔用執行緒),所以會導致使用者應用程式變慢(上線文切換導致的),最終吞吐量下降。

在併發標記、併發清理時,預設產生的 gc threads 個數是 (cpu 個數 + 3)/4,比如雙核 cpu 啟動,會啟動 1 個 gc 執行緒,那麼對 cpu 的佔用可能會達到 50%;5 核心 cpu,會啟動 2 個 gc 執行緒,對 cpu 的佔用可能達到 40%;9 核心 cpu,啟動 3 個 gc 執行緒,對 cpu 的佔用可能達到 33%… 隨著 cpu 核心數的增加,對 cpu 的負擔比例會逐漸下降,但不會低於 25%。

2. 當 CMS 收集器無法處理 “浮動垃圾” 時,會產生 Concurrent Mode Failure 而失敗,導致使用 Serial-Old 替代,重新進行 gc 回收。

在第四步,因為 gc 執行緒與使用者執行緒並行執行,所以在 gc 執行緒執行清理工作的同時,使用者執行緒的執行極可能產生其他的垃圾,這些垃圾無法在本次 GC 中標記、清理。我們稱這些垃圾為浮動垃圾,這些浮動垃圾需要下次清理。

因為有浮動垃圾的存在,所以每次執行 CMS-GC 時,就需要為這些浮動垃圾預留空間,肯定不能等到 old-Gen 滿了再清理。目前預設的 CMS 閾值是 92%,老年代被使用 92% 就開始執行清理工作。

3. 因為 CMS 基於標記 - 清理的演算法實現的,所以會導致 old-Gen 記憶體碎片。當記憶體碎片過多或者 CMS 未完成時 oldGen 已經滿了,則會產生concurrent mode failure然後切換為 SerialOld(mark-sweep-compact 帶整理記憶體碎片)。

The message “concurrent mode failure” signifies that the concurrent
collection ofthe tenured generation did not finish before the
tenured generation became full. In other words, the new generation is
filling up too fast, it is overflowing to tenured generation but the
CMS could not clear out the tenured generation in the background.

就是年輕代晉升到老年代的物件太多,導致 CMS 未完成之前,老年代已經被佔滿了。(也就是擔保記憶體不夠了,無法存放浮動的垃圾,可以減少 - XX:CMSInitiatingOccupancyFraction 指標)

G1 收集器:
http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

http://darktea.github.io/notes/2013/09/08/java-gc.html

https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works/

另外附上一個非常有用的配置, 用來顯示所有的 JVM 引數值 (包括沒有設定的):

-XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version

詳見:
https://www.javaworld.com/article/2073676/hotspot-jvm-options-displayed---xx--printflagsinitial-and--xx--printflagsfinal.html

全文完

本文由簡悅 SimpRead優化,用以提升閱讀體驗 使用了全新的簡悅詞法分析引擎beta點選檢視詳細說明

文章目錄GC 回收哪些記憶體?GC 如何回收?再談引用finalize() 與 FinalizerQueueGC in PergenGC 演算法


深入理解 JVM 一 GC(下) G1 Garbage Collector_張碩的部落格 - CSDN 部落格_garbagecollector

文章目錄

關於 java 程式效能

當我們調優 java 程式時,通常的目標有兩個:
響應能力 或者 吞吐量

響應能力

響應能力指一個程式或者系統對請求的是否能夠及時響應。
比如:
一個桌面 UI 能多快的響應一個事件;
一個網站能夠多快返回一個頁面請求;
資料庫能夠多快返回查詢的資料;

對於這類對響應能力敏感的場景,長時間的停頓是無法接受的。

吞吐量

吞吐量關注的是,在一個指定的時間內,最大化一個應用的工作量。
如下方式來衡量一個系統吞吐量的好壞:

在一小時內同一個事務 (或者任務、請求) 完成的次數(tps)。
資料庫一小時可以完成多少次查詢;

對於關注吞吐量的系統,卡頓是可以接受的,因為這個系統關注長時間的大量任務的執行能力,單次快速的響應並不值得考慮。

應用程式執行實際 / 實際時間 (開始時間戳 - 結束時間戳)

understanding TPS

G1 Garbage Collector

G1 垃圾收集器

g1 收集器是一個面向服務端的垃圾收收集器,適用於多核處理器、大記憶體容量的服務端系統。
它滿足短時間 gc 停頓的同時達到一個高的吞吐量。JDK7 以上版本適用。

g1 收集器的設計目標:

與應用執行緒同時工作,幾乎不需要 stop-the-world(與 CMS 類似);
整理剩餘空間, 不產生記憶體碎片;(CMS 只能在 full-GC 時,用 stop-the-world 整理碎片記憶體)
GC 停頓更加可控;
不犧牲系統的吞吐量;
gc 不要求額外的記憶體空間 (CMS 需要預留空間儲存浮動垃圾);

G1 的設計規劃,是要替換掉 CMS。

G1 在某些方便彌補了 CMS 的不足,比如,CMS 使用的是 mark-sweep 演算法,自然會產生記憶體碎片;然而 G1 基於 copying 演算法,高效的整理剩餘記憶體, 而不需要使用 free-list 去管理記憶體碎片。
另外,G1 提供了更多手段,以達到對 gc 停頓時間可控。

之前的 GC 收集器對 Heap 的劃分:

G1 對 Heap 的劃分:

heap 被劃分為一個個相等的不連續的記憶體區域(regions), 每個 region 都有一個分代的角色: eden、survivor、old(old 還有一種細分 humongous,用來存放大小超過 region 50% 以上的巨型物件)。

但是對每個角色的數量並沒有強制的限定,也就是說對每種分代記憶體的大小,可以動態變化 (預設年輕代佔整個 heap 的 5%)。

G1 最大的特點就是高效的執行回收,優先去執行那些大量物件可回收的區域(region)。

另外,G1 使用了 gc 停頓可預測的模型,來滿足使用者設定的 gc 停頓時間,根據使用者設定的目標時間,g1 會自動的選擇哪些 region 要清楚,一次清除多少個 region。

G1 從多個 region 中複製存活的物件,然後集中放入一個 region 中,同時整理、清除記憶體 (copying 收集演算法)。

注意對比之前的垃圾收集器 (主要是 CMS):
對比使用 mark-sweep 的 CMS,g1 使用的 copying 演算法不會造成記憶體碎片;
對比 ParallelScavenge(基於 copying)、ParallelOld 收集器 (基於 mark-compact-sweep),Parallel
會對整個區域做整理導致 gc Pause 會比較長,而 g1 只是特定的整理幾個 region。

值得注意: g1 不是一個實時的收集器,與 parallelScavenge 一樣,對 gc 停頓時間的設定並不絕對生效,只是 g1 有較高的機率保證不超過設定 gc 停頓時間。與之前的 gc 收集器對比,g1 會根據使用者設定的 gc 停頓時間,智慧評估一下哪幾個 region 需要被回收可以滿足使用者設定。

G1 記憶體的分配

1.TLAB(TLAB 佔用年輕代記憶體). 預設使用 TLAB 加速記憶體分配, 之前文章已經講過,不贅述。
2.Eden. 如果 TLAB 不夠用,則在 Eden 中分配記憶體生成物件。
3.Humongous. 如果物件需要的記憶體超過一個 region 的 50% 以上,會忽略前兩個步驟直接在老年代的 humongous 中分配(連續的 Region)。

何時使用 G1(-XX:+UseG1GC)

1. 大記憶體中為了達到低 gc 延遲.
比如: heap size >=6G,gc pause <=0.5s
2.FullGC 時間太長,或者太頻繁。

調優引數:
-XX:MaxGCPauseMillis=200
使用者設定的最大 gc 停頓時間,預設是 200ms.
-XX:InitiatingHeapOccupancyPercent=45
預設是 45,也就是 heap 中 45% 的容量被使用,則會觸發 concurrent gc。

G1 垃圾回收步驟詳解

G1 提供了兩種 GC 模式,Young GC 和 Mixed GC,兩種都是 Stop The World(STW) 的

G1 Young GC(STW)

1. 當 eden 資料滿了, 則觸發 g1 YGC
2. 並行的執行:
YGC 將 eden region 中存活的物件拷貝到 survivor, 或者直接晉升到 Old Region 中;將 Survivor Regin 中存活的物件拷貝到新的 Survivor 或者晉升 old region。
3. 計算下一次 YGC eden、Survivor 的尺寸

G1 Mix GC

在 G1 GC 中,它主要是為 Mixed GC 提供標記服務的,並不是一次 GC 過程的一個必須環節。global concurrent marking 的執行過程分為五個步驟:

初始標記(initial mark,STW)
在此階段,G1 GC 對根進行標記。該階段與常規的 (STW) 年輕代垃圾回收密切相關。

根區域掃描(root region scan)
G1 GC 在初始標記的存活區掃描對老年代的引用,並標記被引用的物件。該階段與應用程式(非 STW)同時執行,並且只有完成該階段後,才能開始下一次 STW 年輕代垃圾回收。

併發標記(Concurrent Marking)
G1 GC 在整個堆中查詢可訪問的(存活的)物件。該階段與應用程式同時執行,可以被 STW 年輕代垃圾回收中斷

最終標記(Remark,STW)
該階段是 STW 回收,幫助完成標記週期。G1 GC 清空 SATB 緩衝區,跟蹤未被訪問的存活物件,並執行引用處理。

清除垃圾(Cleanup,STW)
在這個最後階段,G1 GC 執行統計和 RSet 淨化的 STW 操作。在統計期間,G1 GC 會識別完全空閒的區域和可供進行混合垃圾回收的區域。清理階段在將空白區域重置並返回到空閒列表時為部分併發。

g1 對老年代回收 - 總結:

1. 併發標記階段 (Concurrent Marking Phase):
在不產生 stop-the-world,與程式程序併發的情況下,活躍度(可達性分析)被分析出來。
活躍度越低,代表回收的效率越高,越值得優先回收。
2. 複製、清理階段 (Copying/Cleanup Phase)
年輕代、老年代在這個階段同時被回收掉。老年代被回收的 region,是根據這個 region 的存活度來選擇的。

更多詳細資訊請點選

全文完

本文由簡悅 SimpRead優化,用以提升閱讀體驗 使用了全新的簡悅詞法分析引擎beta點選檢視詳細說明

文章目錄關於 java 程式效能響應能力吞吐量G1 Garbage Collectorg1 收集器的設計目標:G1 的設計規劃,是要替換掉 CMS。之前的 GC 收集器對 Heap 的劃分:G1 對 Heap 的劃分:G1 記憶體的分配何時使用 G1(-XX:+UseG1GC)G1 垃圾回收步驟詳解G1 Young GC(STW)G1 Mix GCg1 對老年代回收 - 總結: