前言
Java发展至今,最新版本是JDK16,最新的LTS长期支持版本是JDK11,今年9月即将推出JDK17,将是最新一代LTS。 但是,包括笔者在内,绝大多数Java程序猿依然奋战在JDK8一线。 GC(Garbage Collection)垃圾回收作为Java的重要部分,JDK7和JDK8使用的是传统的年轻代与老年代物理空间区分的方式,JDK9将G1作为了默认GC器,JDK11推出了ZGC。
JDK7和JDK8的GC
JDK7将堆空间分为年轻代、老年代、永久代,JDK8删除了永久代,取而代之的是堆外的元空间。 回收算法主要有复制清除(Copying)算法(年轻代)、标记整理(Mark-Compact)算法(老年代)、标记清除(Mark-Sweep)算法。
G1
G1全称为Garbage First,从名字就看得出来,这个GC不简单。 G1最大的改变是打破了年轻代与老年代的物理空间壁垒,但保留了逻辑上的区分。
Region
G1将堆空间分为一个一个方形区域(Region)。可通过参数-XX:G1HeapRegionSize设置单个Region的大小,取值范围1M-32M,2的n次幂。默认为堆内存/2048,即一共2048个Region。 一段连续的空间,根据需要可扮演不同角色,可以是年轻代的伊甸区(E)、幸存区(S),也可以是老年代(O)。 除了E、S、O,还有一种代号为H的Region,是以往算法里没有的概念。H代表Humongous,即大对象。当一个对象的大小超过了Region的一半,则视为大对象,如果超过了整个Region的大小,将使用连续N个H Region来存放。虽说Humongous跳出了年轻代和老年代的概念,但G1的大多数行为都把它视为老年代的一部分。
GC模式
G1出现之前,GC范围是整个年轻代(Minor GC)、整个老年代(Major GC)、整个堆空间(Full GC)。 G1的GC模式有Young GC、Mixed GC、Full GC。
Young GC
除了大对象在H Region分配空间,一般对象都在E Region分配。当所有E Region被耗尽,触发一次Young GC,执行完后,活跃对象会被copy到S Region或晋升到O Region,空闲的E Region加入空闲列表。 可以看到,G1的Young GC与G1出现之前的Young GC,逻辑基本一致。
Mixed GC
G1可以回收堆空间的任何部分,组成回收集(Collection Set、CSet)来回收,衡量标准不再是属于哪个分代,而是哪块内存中垃圾数量最多,回收收益最大,这就是Mixed GC。 Mixed GC回收所有E Region、S Region和部分的O Region及H Region。 可通过参数-XX:InitiatingHeapOccupancyPercent设置阈值,当O Region占整个堆空间百分比达到此阈值,触发一次Mixed GC,包括以下几步:
- 初始标记(Initial Mark):标记从GC Root可达的对象。此阶段会STW(Stop The World)。
- 并发标记(Concurrent Mark):采用三色标记法,标记整个堆空间的可访问的对象。此阶段不会STW,与应用程序同时运行。此阶段时间长,约占整个Mixed GC的80%
- 最终标记(Remark):标记并发标记阶段遗漏或引用发生变化的对象。此阶段会STW。
- 垃圾清除(Cleanup):空闲的Region加入空闲列表。此阶段会STW。
Full GC
如果对象分配内存太快,Mixed GC来不及回收,导致O Region满载,触发一次Full GC。 此阶段会STW,单线程执行标记、清理和压缩整理,因此会导致STW时间过长,需调优尽量避免。(JDK12的Shenandoah GC对此有优化?)
ZGC
先贴一段官方简介:
The Z Garbage Collector, also known as ZGC, is a scalable low latency garbage collector designed to meet the following goals:
- Sub-millisecond max pause times
- Pause times do not increase with the heap, live-set or root-set size
- Handle heaps ranging from a 8MB to 16TB in size
At a glance, ZGC is:
- Concurrent
- Region-based
- Compacting
- NUMA-aware
- Using colored pointers
- Using load barriers
At its core, ZGC is a concurrent garbage collector, meaning all heavy lifting work is done while Java threads continue to execute. This greatly limits the impact garbage collection will have on your application’s response time.
The goal of this project is to create a scalable low latency garbage collector capable of handling heaps ranging from a few gigabytes to multi terabytes in size, with GC pause times not exceeding 10ms.
由此可见,ZGC可满足:亚毫秒级的最大暂停时间、堆空间大小等的增加不会影响效率、堆空间最大可达16T(JDK13由4T增至16T)。 与G1一样,ZGC也基于Region,可与应用程序并行。此外,ZGC的最大暂停时间不超过10毫秒,JDK13新增了将未提交内存还给操作系统的特效。
Region
与G1不同,ZGC不会初始化就分配固定大小,更加动态。 ZGC的Region分为:
- 小Region(Small Region):容量固定2M,存放<2M的对象。
- 中Region(Medium Region):容量固定32M,存放>=2M但<4M的对象。
- 大Region(Large Region):容量不固定,可变,但必须是2M的整数倍,存放>=4M的对象。每个大Region只存放一个对象,GC时直接回收。
GC模式
- 初始标记(Initial Mark):标记从GC Root可达的对象。此阶段会STW。
- 并发标记(Concurrent Mark):采用颜色指针标记法,标记整个堆空间的可访问的对象。此阶段不会STW。
- 最终标记(Remark):标记并发标记阶段遗漏或引用发生变化的对象。此阶段会STW。
- 并发预备重分配(Concurrent Prepare For Relocate):根据特定查询条件扫描所有Region,获取本次回收过程需清理哪些Region,重新组成重分配集(Relocation Set)。此阶段不会STW。
- 并发初始重分配(Relocate Start):并发重分配阶段的初始化。此阶段会STW。
- 并发重分配(Concurrent Relocate):将并发预备重分配阶段重分配集中的Region复制到新的Region,并为每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的关系。此阶段不会STW。
- 并发重映射(Concurrent Remap):将旧地址转换为新地址。此阶段不会STW。
一些感悟
- 虽然绝大多数Java程序猿依然坚守在JDK8,JDK8也越来越稳定,但是,我们还是应拥抱新技术,即使是非LTS版本,每次JDK的大版本新特性和bug修复,还是值得细细品味。
- 对于GC这件事,STW从1秒到100毫秒、10毫秒,甚至1毫秒,JDK官方依然在不断精进。G1可谓推陈出新,后续诸如ZGC更是基于G1玩出了花,理解难度也随之加深。
文末彩蛋
依然来自ZGC官方文档。
What does the “Z” in ZGC stand for? It doesn’t stand for anything, ZGC is just a name. It was originally inspired by, or a homage to, ZFS (the filesystem) which in many ways was revolutionary when it first came out. Originally, ZFS was an acronym for “Zettabyte File System”, but that meaning was abandoned and it was later said to not stand for anything. It’s just a name.
Is it pronounced “zed gee see” or “zee gee see”? There’s no preferred pronunciation, both are fine.
作者:曼特宁
|