ZGC(Z Garbage Collector)是Java平台上的一种垃圾收集器,它是由Oracle开发的,旨在解决大堆的低延迟垃圾收集问题。ZGC是一种并发的分代垃圾收集器,它主要针对具有大内存需求和低停顿时间要求的应用程序
ZGC的核心概念及对应的JVM参数详见 分代ZGC详解
分代ZGC收集器具备以下特性:
- 没有多重映射内存
- 内存屏障优化
- 双重缓冲记忆集
- 无需额外堆内存重分配
- 堆区域密度
- 大对象处理
ZGC 垃圾回收过程
ZGC使用了一种称为并发压缩的技术,在垃圾回收过程中进行垃圾对象的压缩和内存整理。下面是ZGC垃圾回收的大致过程:
- 初始标记:ZGC会标记出所有的根对象,包括线程栈上的引用、静态变量和一些特殊的对象。初始标记的目的是确定一组初始的存活对象,以便在后续的并发标记和重定位阶段中进行处理
- 并发标记/对象重定位:并发地遍历堆中的对象,并标记出这些对象的存活状态。同时,ZGC会将存活对象从旧的内存区域重定位到新的内存区域,以便为后续的对象分配提供更大的连续空间。这个阶段的并发执行可以减少垃圾回收对应用程序的停顿时间
- 再标记:在并发标记阶段期间,应用程序可能会继续产生新的对象,而这些新对象也需要被标记为存活。因此,ZGC需要进行一次再标记阶段,以标记并更新在并发标记期间产生的新对象
- 并发转移准备:在并发转移准备阶段,ZGC会准备进行并发的对象转移。这包括处理与并发标记阶段重叠的一些操作,例如更新引用和处理根对象。这个阶段的目的是为并发转移阶段做准备,以确保在该阶段的并发执行期间,应用程序的执行不会受到过多的停顿。
- 初始转移:ZGC会将剩余的存活对象从旧的内存区域转移到新的内存区域。
- 并发转移:ZGC会在后台并发地处理剩余的存活对象,并将它们从旧的内存区域转移到新的内存区域。与并发标记/对象重定位阶段一样,这个阶段的并发执行可以最大程度地减少应用程序的停顿时间
在ZGC的整个垃圾回收过程中,大部分工作都是与应用程序线程并发执行的,因此ZGC的主要特点是低延迟和高吞吐量,适合对响应时间有较高要求的应用场景
ZGC只有三个STW阶段:初始标记,再标记,初始转移。 其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加
ZGC 日志分析
GC(4125) Heap before GC invocations=4125 (full 4125):
GC(4125) ZHeap used 1942M, capacity 4536M, max capacity 4536M
GC(4125) Metaspace used 146734K, committed 148096K, reserved 344064K
GC(4125) class space used 17468K, committed 18240K, reserved 212992K
GC(4125) Garbage Collection (Proactive)
GC(4125) Using 1 workers
GC(4125) Pause Mark Start 0.041ms
Safepoint "XMarkStart", Time since last: 22124432085 ns, Reaching safepoint: 17382 ns, Cleanup: 6329 ns, At safepoint: 76852 ns, Total: 100563 ns
GC(4125) Concurrent Mark 322.101ms
GC(4125) Pause Mark End 0.022ms
Safepoint "XMarkEnd", Time since last: 322171078 ns, Reaching safepoint: 9147 ns, Cleanup: 8722 ns, At safepoint: 52553 ns, Total: 70422 ns
GC(4125) Concurrent Mark Free 0.001ms
GC(4125) Concurrent Process Non-Strong References 41.260ms
GC(4125) Concurrent Reset Relocation Set 0.125ms
Safepoint "CleanClassLoaderDataMetaspaces", Time since last: 41168753 ns, Reaching safepoint: 76926 ns, Cleanup: 6011 ns, At safepoint: 3152494 ns, Total: 3235431 ns
GC(4125) Concurrent Select Relocation Set 5.057ms
GC(4125) Pause Relocate Start 0.019ms
Safepoint "XRelocateStart", Time since last: 2142288 ns, Reaching safepoint: 16014 ns, Cleanup: 4139 ns, At safepoint: 60372 ns, Total: 80525 ns
GC(4125) Concurrent Relocate 41.819ms
GC(4125) Heap after GC invocations=4126 (full 4126):
GC(4125) ZHeap used 242M, capacity 4536M, max capacity 4536M
GC(4125) Metaspace used 146734K, committed 148096K, reserved 344064K
GC(4125) class space used 17468K, committed 18240K, reserved 212992K
GC(4125) Load: 7.70/8.06/8.36
GC(4125) MMU: 2ms/85.9%, 5ms/94.4%, 10ms/97.2%, 20ms/98.6%, 50ms/99.4%, 100ms/99.7%
GC(4125) Mark: 1 stripe(s), 2 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
GC(4125) Mark Stack Usage: 32M
GC(4125) NMethods: 11693 registered, 10397 unregistered
GC(4125) Metaspace: 143M used, 144M committed, 336M reserved
GC(4125) Soft: 3566 encountered, 954 discovered, 0 enqueued
GC(4125) Weak: 21037 encountered, 7964 discovered, 6855 enqueued
GC(4125) Final: 45 encountered, 8 discovered, 0 enqueued
GC(4125) Phantom: 757 encountered, 499 discovered, 10 enqueued
GC(4125) Small Pages: 924 / 1848M, Empty: 8M, Relocated: 26M, In-Place: 0
GC(4125) Medium Pages: 2 / 64M, Empty: 32M, Relocated: 0M, In-Place: 0
GC(4125) Large Pages: 5 / 30M, Empty: 0M, Relocated: 0M, In-Place: 0
GC(4125) Forwarding Usage: 10M
GC(4125) Min Capacity: 4536M(100%)
GC(4125) Max Capacity: 4536M(100%)
GC(4125) Soft Max Capacity: 4536M(100%)
GC(4125) Mark Start Mark End Relocate Start Relocate End High Low
GC(4125) Capacity: 4536M (100%) 4536M (100%) 4536M (100%) 4536M (100%) 4536M (100%) 4536M (100%)
GC(4125) Free: 2594M (57%) 2564M (57%) 2600M (57%) 4294M (95%) 4294M (95%) 2560M (56%)
GC(4125) Used: 1942M (43%) 1972M (43%) 1936M (43%) 242M (5%) 1976M (44%) 242M (5%)
GC(4125) Live: - 157M (3%) 157M (3%) 157M (3%) - -
GC(4125) Allocated: - 30M (1%) 34M (1%) 38M (1%) - -
GC(4125) Garbage: - 1784M (39%) 1744M (38%) 45M (1%) - -
GC(4125) Reclaimed: - - 40M (1%) 1738M (38%) - -
GC(4125) Garbage Collection (Proactive) 1942M(43%)->242M(5%)
关键点中文解释
- GC(4125) HEAP BEFORE GC invocations=4125 (FULL 4125): 执行垃圾回收前的引用数
- GC(4125) Garbage Collection (Proactive): 执行的是一次主动的垃圾回收。
- GC(4125) USING 1 workers: 使用了1个工作线程。
- GC(4125) CONCURRENT Mark 322.101ms: 并发标记阶段的耗时
- GC(4125) CONCURRENT PROCESS Non-Strong REFERENCES 41.260ms: 并发处理非强引用的耗时
- GC(4125) CONCURRENT RESET Relocation SET 0.125ms: 再标记阶段的耗时
- GC(4125) CONCURRENT SELECT Relocation SET 5.057ms: 并发选择回收区域的耗时
- GC(4125) CONCURRENT Relocate 41.819ms: 并发转移阶段的耗时
- GC(4125) HEAP AFTER GC invocations=4126 (FULL 4126): 执行垃圾回收后的引用数
ZGC 垃圾收集统计
Last 10s | Last 10m | Last 10h | Total | 单位 | |
---|---|---|---|---|---|
Avg / Max | Avg / Max | Avg / Max | Avg / Max | ||
Collector: Garbage Collection Cycle | 411.071 / 411.071 | 426.927 / 571.956 | 395.057 / 615.642 | 396.554 / 794.838 | ms |
Contention: Mark Segment Reset Contention | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | ops/s |
Contention: Mark SeqNum Reset Contention | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | ops/s |
Critical: Allocation Stall | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | ops/s |
Critical: Allocation Stall | 0.000 / 0.000 | 0.000 / 0.000 | 0.000 / 0.000 | 0.000 / 0.000 | ms |
Critical: GC Locker Stall | 0 / 0 | 0 / 0 | 0 / 1 | 0 / 1 | ops/s |
Critical: GC Locker Stall | 0.000 / 0.000 | 0.000 / 0.000 | 0.182 / 1.080 | 0.135 / 1.156 | ms |
Critical: Relocation Stall | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | ops/s |
Critical: Relocation Stall | 0.000 / 0.000 | 0.000 / 0.000 | 0.000 / 0.000 | 0.000 / 0.000 | ms |
Memory: Allocation Rate | 83 / 140 | 74 / 148 | 51 / 218 | 52 / 368 | MB/s |
Memory: Defragment | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | ops/s |
Memory: Out Of Memory | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | ops/s |
Memory: Page Cache Flush | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | MB/s |
Memory: Page Cache Hit L1 | 41 / 59 | 37 / 67 | 25 / 109 | 26 / 184 | ops/s |
Memory: Page Cache Hit L2 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | ops/s |
Memory: Page Cache Hit L3 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 63 | ops/s |
Memory: Page Cache Miss | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 1 | ops/s |
Memory: Uncommit | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | MB/s |
Memory: Undo Object Allocation Failed | 6 / 63 | 4 / 447 | 1 / 655 | 1 / 1196 | ops/s |
Memory: Undo Object Allocation Succeeded | 21 / 210 | 5 / 482 | 2 / 605 | 2 / 751 | ops/s |
Memory: Undo Page Allocation | 0 / 3 | 0 / 3 | 0 / 5 | 0 / 7 | ops/s |
Phase: Concurrent Mark | 322.101 / 322.101 | 336.304 / 443.879 | 317.178 / 518.134 | 316.142 / 596.839 | ms |
Phase: Concurrent Mark Continue | 0.000 / 0.000 | 0.000 / 0.000 | 0.000 / 0.000 | 0.000 / 0.000 | ms |
Phase: Concurrent Mark Free | 0.001 / 0.001 | 0.001 / 0.001 | 0.001 / 0.031 | 0.001 / 0.031 | ms |
Phase: Concurrent Process Non-Strong References | 41.260 / 41.260 | 39.545 / 74.002 | 36.013 / 74.002 | 37.811 / 128.820 | ms |
Phase: Concurrent Relocate | 41.819 / 41.819 | 44.695 / 54.799 | 35.851 / 61.680 | 36.447 / 154.466 | ms |
Phase: Concurrent Reset Relocation Set | 0.125 / 0.125 | 0.130 / 0.182 | 0.082 / 0.287 | 0.096 / 0.539 | ms |
Phase: Concurrent Select Relocation Set | 5.057 / 5.057 | 5.580 / 6.714 | 5.248 / 9.577 | 5.375 / 96.450 | ms |
Phase: Pause Mark End | 0.022 / 0.022 | 0.027 / 0.061 | 0.024 / 0.077 | 0.024 / 0.103 | ms |
Phase: Pause Mark Start | 0.041 / 0.041 | 0.035 / 0.049 | 0.036 / 0.281 | 0.035 / 0.281 | ms |
Phase: Pause Relocate Start | 0.019 / 0.019 | 0.021 / 0.030 | 0.020 / 0.076 | 0.020 / 0.096 | ms |
Subphase: Concurrent Classes Purge | 0.008 / 0.008 | 0.031 / 0.203 | 0.018 / 1.835 | 0.072 / 67.248 | ms |
Subphase: Concurrent Classes Unlink | 34.205 / 34.205 | 32.226 / 65.288 | 29.823 / 65.288 | 31.479 / 89.630 | ms |
Subphase: Concurrent Mark | 316.043 / 316.043 | 330.668 / 437.551 | 311.415 / 512.480 | 310.233 / 588.118 | ms |
Subphase: Concurrent Mark Try Flush | 0.120 / 0.247 | 0.113 / 0.249 | 0.114 / 3.157 | 0.116 / 8.934 | ms |
Subphase: Concurrent Mark Try Terminate | 0.028 / 0.055 | 0.029 / 0.076 | 0.031 / 0.433 | 0.031 / 0.657 | ms |
Subphase: Concurrent References Enqueue | 0.006 / 0.006 | 0.006 / 0.010 | 0.005 / 0.045 | 0.006 / 0.064 | ms |
Subphase: Concurrent References Process | 2.721 / 2.721 | 2.955 / 4.341 | 1.961 / 5.211 | 2.033 / 37.505 | ms |
Subphase: Concurrent Roots ClassLoaderDataGraph | 0.697 / 0.697 | 0.626 / 0.778 | 0.619 / 1.592 | 0.635 / 1.764 | ms |
Subphase: Concurrent Roots CodeCache | 0.000 / 0.000 | 0.000 / 0.000 | 0.000 / 0.000 | 0.000 / 0.000 | ms |
Subphase: Concurrent Roots JavaThreads | 5.053 / 5.053 | 4.708 / 5.180 | 4.889 / 91.078 | 5.013 / 91.078 | ms |
Subphase: Concurrent Roots OopStorageSet | 0.162 / 0.162 | 0.138 / 0.162 | 0.141 / 1.029 | 0.144 / 1.029 | ms |
Subphase: Concurrent Weak Roots OopStorageSet | 3.957 / 3.957 | 3.962 / 4.589 | 3.840 / 6.333 | 3.849 / 8.860 | ms |
Subphase: Pause Mark Try Complete | 0.000 / 0.000 | 0.016 / 0.016 | 0.011 / 0.026 | 0.010 / 0.027 | ms |
System: Java Threads | 87 / 87 | 87 / 89 | 87 / 91 | 86 / 112 | thread |
G1/ZGC 回收过程对比
G1的混合回收过程可以分为标记阶段、清理阶段和复制阶段。
标记阶段停顿分析
-
初始标记阶段:初始标记阶段是指从GC Roots出发标记全部直接子节点的过程,该阶段是STW的。由于GC Roots数量不多,通常该阶段耗时非常短。
-
并发标记阶段:并发标记阶段是指从GC Roots开始对堆中对象进行可达性分析,找出存活对象。该阶段是并发的,即应用线程和GC线程可以同时活动。并发标记耗时相对长很多,但因为不是STW,所以我们不太关心该阶段耗时的长短。
-
再标记阶段:重新标记那些在并发标记阶段发生变化的对象。该阶段是STW的。
清理阶段停顿分析 -
清理阶段:清点出有存活对象的分区和没有存活对象的分区,该阶段不会清理垃圾对象,也不会执行存活对象的复制。该阶段是STW的。
复制阶段停顿分析
- 复制算法中的转移阶段需要分配新内存和复制对象的成员变量。转移阶段是STW的,其中内存分配通常耗时非常短,但对象成员变量的复制耗时有可能较长,这是因为复制耗时与存活对象数量与对象复杂度成正比。对象越复杂,复制耗时越长
四个STW过程中,初始标记因为只标记GC Roots,耗时较短。再标记因为对象数少,耗时也较短。清理阶段因为内存分区数量少,耗时也较短。转移阶段要处理所有存活的对象,耗时会较长。因此,G1停顿时间的瓶颈主要是标记-复制中的转移阶段STW
ZGC只有三个STW阶段:初始标记,再标记,初始转移。ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加
与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加
参考资料:
- 分代ZGC详解
- 新一代垃圾回收器ZGC的探索与实践
- 分代ZGC
- G1 垃圾收集器详解