G1垃圾回收器

G1垃圾回收器


G1垃圾回收器

Garbage-First(G1,垃圾优先)收集器是服务类型的收集器,目标是多处理器机器、大内存机器。它高度符合垃圾收集暂停时间的目标,同时实现高吞吐量。Oracle JDK 7 update 4 以及更新发布版完全支持G1垃圾收集器。G1垃圾回集器为以下应用设计:

  • 类似CMS收集器,可以和应用线程同时并发的执行
  • 压缩空闲空间时没有GC引起的暂停时间
  • 需要更可预言的GC暂停时间
  • 不想牺牲大量的吞吐量性能
  • 不需要特别大的Java堆

G1垃圾收集器计划长期替换并发标记清除收集器(CMS,Concurrent Mark-Sweep Collector)。G1和CMS比较,有一些不同点让G1成为一个更好的解决方案。一个不同点是G1是一个压缩收集器。G1收集器充分地压缩空间以完全避免为分配空间使用细粒度的空闲列表,而不是依赖于区块。这相当简化了收集器的部件,和尽量消除可能的碎片问题。同时,G1收集器相比CMS收集器而方言,提供更可预言的垃圾收集暂停时间,允许用户指定想要暂停时间指标。

G1收集器操作概览

旧的垃圾收集器(串行的:serial,并行的:parallel,并发标记清除:CMS)都把堆结构化为三个部分:年轻代、年老代和固定大小的永久代。 

所以内存对象最终都在这三个区域里。 
G1收集器应用了一个不同的方法。 

堆空间被分割成一些相同大小的堆区域,每一个都是连续范围的虚拟内存。特定的区域集合像旧的收集器一样被指派为相同的角色(伊甸:eden、幸存:survivor、年老:old),但是它们没有一个固定大小。这在内存使用上提供了更强大的灵活性。

当执行垃圾收集时,G1收集器以与CMS收集器类似的方式操作。G1收集器执行一个全局的并发标记阶段来决定堆中的对象的活跃度。之后标记阶段就完成了。G1收集器知道哪个区域基本上是空的。它首先会收集那些产出大量空闲空间的区域。这就是为什么这个垃圾收集的方法叫做垃圾优先的原因。就像名称显示的那样,G1收集器集中它的收集和压缩活动在堆里的那些可完全被回收的区域,那就是垃圾。G1收集器使用一个暂停预言的模式去达到一个用户定义的暂停时间指标,基于用户指定的暂停时间指标去选择收集区域的数量。

被G1收集器鉴定为可以回收的区域就是垃圾,使用抽空的方式收集。G1收集器从堆空间的一个或多个区域里复制对象到堆空间的一个单独的区域内,这个过程中同时压缩和释放内存。这个抽空过程在多处理上以并行的方式运行,以减小暂停时间和增加吞吐量。因此,每一次垃圾收集G1收集器连续不断地去减少碎片,在用户指定的暂停时间内工作。这超越了以往方法的能力。并发标记-清除(CMS,Concurrent Mark Sweep)垃圾收集器不做压缩操作。并行年老代(ParallelOld)垃圾收集只进行整个堆的压缩,会导致相当大的暂停时间。

注意: G1收集器不是实时的收集器非常重要。它在很大程度上符合用户设定的暂停时间指标但是并不绝对符合。基于前面垃圾收集的数据来看,G1收集器会估算在用户指定的时间指标能收集多少区域。因此,收集器有一个合理的精确的收集这些区域的代价模型,它使用这个模型决定在用户指定的暂停时间内收集哪些、多少个区域。

注意: G1收集器同时有并发(和应用线程一起运行,比如,提炼、标记、清理)和并行(多线程,比如,stop the world)两个阶段。全量垃圾回收仍然是单线程的,但是如果调优的适当你的应用应该会避免全量垃圾回收。

G1回收器足迹

如果你从ParallelOldGc或者CMS收集器迁移到G1收集器,你很有可能会看到一个大的Java虚拟机进程大小,这和审计”数据结构比如已记忆集合(Remembered Sets)和收集集合(Collection Sets)有很大关系“。

Remembered Sets或者RSets把对象引用推进一个给定的区域。在堆空间中每一个区有一个RSet。RSet允许一个区域并行的、独立的收集。RSet总体的足迹影响小于5%。

Collection Sets或者CSets,是在垃圾回收过程中会被回收的区域集合。在RSet中的所有活跃对象在垃圾回收过程中会被抽空(复制/移动)。集合包含的区域可以是eden、survivor或者年老代。CSets在Java虚拟机大小的影响小于1%。

建议使用G1收集器的场景

G1收集器首要关注的是为用户运行着需要大堆空间、限制的垃圾回收延迟的应用提供一个解决方案。这意味着堆大小为6GB左右或者更大,稳定的、可预言的暂停时间小于0.5秒。

如果应用有以下一个或多个特点,当下运行着CMS或ParallelOldGC垃圾收集器的应用把收集器切换到G1收集器的话,会从中受益的:

  • Full GC持续时间太长或者太频繁
  • 对象分配比率或者提升有显著的变化
  • 不期望的长时间垃圾收集或者压缩暂停(大于0.5到1秒)

**注意:**如果你在使用CMS或者ParallenOldGC收集器,你的应用不曾经历过长时间的垃圾收集暂停,保持使用你当前的收集器比较好。在使用最新的JDK的情况下,改变到G1收集器不是一个必要的事情。

回顾CMS垃圾回收


回顾分代垃圾回收和CMS

并发标记清除(CMS)收集器(也叫并发低延迟收集器)回收年老代垃圾。它通过和应用线程并发的执行大部分垃圾收集工作的方式来尝试最小化垃圾回收引起的暂停。正常情况下并发低延迟收集器不会复制或者压缩活跃对象。一次垃圾收集的完成不必移动活跃对象。如果内存碎片成为一个问题,分配更大的堆空间。

注意: CMS收集器在年轻代上使用和并行收集器相同的算法。

CMS收集阶段

CMS收集器在堆的年老代空间上执行以下阶段:

阶段描述
(1)初始标记(Stop the World事件)年老代里的对象被标记为可达的包括那些可能从年轻代可达的对象。此期间暂停时间相对minor gc的暂停时间是比较 短的
(2)并发标记当Java应用线程运行时,并发的遍历年老代对象图可达的对象。从标记的对象和根上可达到标记对象开始扫描。设值方法在并发的2、3、5阶段期间执行,在这些阶段(包括晋升的对象)被分配进CMS代所有对象都会立刻被标记为活跃对象。
(3)重新标记(Stop the World事件)寻找那些在并发标记阶段丢失的,在并发收集器完成之后跟踪对象之后由Java应用线程的更新的对象。
(4)(并发清除)收集在标记阶段被鉴定为不可达的对象。收集死亡对象会增加空闲列表的空间,方便之后的对象分配。聚合死亡对象可以会在此点发生。注意活跃对象是不会被移动。
(5)(重新设置)清理数据结构为下一次并发收集做准备
回顾垃圾收集步骤

接下来,让我们一步一步的回顾下CMS收集器的操作步骤

1. CMS收集器堆结构

堆空间被分割为三块空间。 

年轻代分割成一个Eden区和两个Survivor区。年老代一个连续的空间。就地完成对象收集。除非有FullGC否则不会压缩。

2.CMS年轻代垃圾收集如何工作

年轻代被标为浅绿色,年老代被标记为蓝色。如果你的应用已经运行了一段时间,CMS的堆看起来应该是这个样子。对象分散在年老代区域里。 

使用CMS,年老代对象就地释放。它们不会被来回移动。这个空间不会被压缩除非发生FullGC。

3.年轻代收集

从Eden和Survivor区复制活跃对象到另一个Survivor区。所有达到他们的年龄阈值的对象会晋升到年老代。 

4.年轻代回收之后

一次年轻代垃圾收集之后,Eden区和其中一个Survivor区被清空。 

最近晋升的对象以深蓝色显示在上图中,绿色的对象是年轻代幸免的还没有晋升到老年代对象。

5.CMS的年老代收集

发生两次stop the world事件:初始标记和重新标记。当年老代达到特定的占用比例时,CMS开始执行。 

(1)初始标记是一个短暂暂停的、可达对象被标记的阶段。(2)并发标记寻找活跃对象在应用连续执行时。最后,在(3)重新标记阶段,寻找在之前并发标记阶段中丢失的对象。

6.年老代收集-并发清除

在之前阶段没有被标记的对象会被就地释放。不进行压缩操作。 

**注意:**未被标记的对象等于死亡对象

7.年老代收集-清除之后

(4)清除阶段之后,你可以看到大量内存被释放。你还可以注意到没有进行压缩操作。 

最后,CMS收集器会走过(5)重新设置阶段,等待下一次垃圾收集时机的到来。

循序渐进G1垃圾收集器


循序渐进G1垃圾收集器

G1收集器在分配堆空间的方法上有些不同。下面的图片一步一步系统的回顾G1收集器。

1.G1堆结构

堆空间是一个被分成许多固定大小区域的内存块。 

Java虚拟机启动时选定区域大小。Java虚拟机通常会指定2000个左右的大小相等、每个大小范围在1到32M的区域。

2.G1堆空间分配

实际上,这些区域被映射成Eden、Survivor、年老代空间的逻辑表述形式。 

图片中的颜色表明了哪个区域被关联上什么角色。活跃对象从一个区域疏散(复制、移动)到另一个区域。区域被设计为并行的方式收集,可以暂停或者不暂停所有的其它用户线程。

明显的区域可以被分配成Eden、Survivor、Old区域。另外,有第四种类型的区域叫做极大区域(Humongous regions)。这些区域被设计成保持标准区域大小的50%或者更大的对象。它们被保存在一个连续的区域集合里。最后,最后一个类型的区域就是堆空间里没有使用的区域。

**注意:**写作此文章时,收集极大对象时还没有被优化。因此,你应该避免创建这个大小的对象。

3.G1的年轻代

堆空间被分割成大约2000个区域。最小1M,最大32M,蓝色区域保持年老代对象,绿色区域保持年轻代对象。 

**注意:**区域没有必要像旧的收集器一样是保持连续的。

4.G1的年轻代收集

活跃对象会被疏散(复制、移动)到一个或多个survivor区域。如果达到晋升总阈值,对象会晋升到年老代区域。 

这是一个stop the world暂停。为下一次年轻代垃圾回收计算Eden和Survivor的大小。保留审计信息有助于计算大小。类似目标暂停时间的事情会被考虑在内。

这个方法使重调区域大小变得很容易,按需把它们调大或调小。

5.G1年轻代回收的尾声

活跃对象被疏散到Survivor或者年老代区域。 

最近晋升的对象显示为深蓝色。Survivor区域显示为绿色。

关于G1的年轻代回收做以下总结:

  • 堆空间是一块单独的内存空间被分割成多个区域。
  • 年轻代内存是由一组非连续的区域组成。这使得需要重调大小变得容易。
  • 年轻代垃圾回收是stop the world事件,所有应用线程都会因此操作暂停。
  • 年轻代垃圾收集使用多线程并行回收。
  • 活跃对象被复制到新的Survivor区或者年老代区域。

G1年老代垃圾回收

类似CMS收集器,G1收集器为年老代对象被设计成一个低暂停收集器。下面的表描述了在年老代上的G1收集阶段。 
G1垃圾收集器在堆上的年老代执行以下阶段。注意一些阶段是年轻代回收的一部分。

阶段描述
(1)初始标记(stop the world事件)这是一个stop the world事件,使用G1回收器,背负着一个常规的年轻代收集。标记那些有引用到年老代的对象的survivor区(根区)
(2)根区扫描为到年老代的引用扫描survivor区,这个发生在应用继续运行时。这个阶段在年轻代收集前必须完成
(3)并发标记遍历整个堆寻找活跃对象,这个发生在应用运行时,这个阶段可以被年轻代垃圾回收打断。
(4)重新标记(stop the world事件)完全标记堆中的活跃对象,使用一个叫作snapshot-at-the-beginning(SATB)的比CMS收集器的更快的算法
(5)清理(stop the world事件和并发)在活跃对象上执行审计操作和释放区域空间(stop the world);净化已记忆集合(stop the world);重置空间区域和返回它们到空闲列表(并发)
(*)复制(stop the world事件)这些是stop the world暂停为了疏散或者复制活跃对象到新的未使用的区域。这个可以由被记录为[GC Pause (young)]的年轻代区域或者被记录为[GC Pause (mixed)]年轻代和年老代区域完成

循序渐进G1年老代垃圾回收

记住已被定义的阶段,让我们来看一下G1收集器是如何作用于年老代的。

6.初始标记阶段

年轻代垃圾收集肩负着活跃对象初始标记的任务。在日志文件中被标为GC pause (young)(inital-mark)

7.并发标记阶段

如果发现空区域(“X”标示的),在重新标记阶段它们会被马上清除掉。当然,决定活性的审计信息也在此时被计算。 

8.重新标记阶段

空的区域被清除和回收掉。所有区域的活性在此时计算。 

9.复制/清理阶段

G1选择活性最低的区域,这些区域能够以最快的速度回收。然后这些区域会在年轻代垃圾回收过程中被回收。在日志中被指示为*[GC pause (mixed)]*。所以年轻代和年老代在同一时间被回收。 

10.复制/清理阶段之后

被选择的区域已经被回收和压缩到图中显示的深蓝色区和深绿色区中。 

年老代垃圾回收总结

总结下,我们可以列出一些关于G1收集器在年老代的上关键点。 
并发标记阶段

  • 当应用运行时,并发的计算活性信
  • 在疏散暂停期间,活性信息鉴定哪些区被最好的回收
  • 没有像CMS一样的清除操作

重新标记阶段

  • 使用比在CMS中使用的算法更快的Snapshot-at-the-Beginning(SATB)算法
  • 完全空的区域会被回收掉

复制/清理阶段

  • 年轻代和年老代被同时回收
  • 年老代区域基于它们的活性被选择

命令行选项最佳实践


命令行选项最佳实践

在这部分我们看一下G1收集器的多样的命令行选项。

基本命令行

为了启用G1收集器,使用:-XX:+UseG1GC
这个是启动在已下载的JDK演示和示例里的Java2Demo程序的示例命令行: 
java -Xmx50m -Xms50m -XX:UserG1GC -XX:MaxGCPauseMillis=200 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar

关键命令行开关

-XX:+UseG1GC - 告诉Java虚拟机使用G1垃圾收集器 
-XX:MaxGCPauseMillis=200 - 为最大GC暂停时间设置一个指标。这是一个软目标,Java虚拟机将尽最大努力实现它。因此,暂停时间目标有时候可能不会达到。默认值是200毫秒。 
-XX:InitiatingHeapOccupancyPercent=45 - 触发并发垃圾收集周期的整个堆的百分比时机。

最佳实践

使用G1收集器时你应该遵守的一些最佳实践 
不要设置年轻代大小
通过**-Xmn**明确地设置年轻代大小来插手G1收集器的默认行为。

  • 收集时G1收集器将不再遵照暂停时间指标。所以本质上,设置年轻代大小将不会启用暂停时间目标。
  • G1收集器将不能按需扩张、收缩年轻代空间。自从大小被固定之后,大小将不再会被改变。
响应时间指标

代替使用平均响应时间(ART)做为指标,来设置XX:MaxGCPauseMillis=,考虑设置值将会符合这个时间的90%或者更高比例。这意味着90%的用户发出一个请求将不会经历高于这个目标的时间。记住,暂停时间只是一个目标,不保证总是能够达到。

什么是疏散失败?

当Java虚拟机在Survivor和晋升的对象垃圾回收期间,堆空间用光了就会发生晋升失败。堆空间不能再扩展了因为已经在最大值了,使用**-XX:+PrintGCDetails参数时,这种情况会在GC日志中通过to-space-overflow**指示出来。这个代价非常大。

  • 垃圾收集仍然会继续运行,空间必须被释放。
  • 没有成功复制的对象必须就地被提升。
  • 在CSet里的任何到区域的RSets的更新都会重新生成
  • 所有这些步骤代价都非常大
如何避免疏散失败

为了避免疏散失败,考虑以下选项。 
增大堆大小

  • 增大**-XX:G1ReservePercent=n**参数值,默认是10
  • G1收集器创建一个假的上限通过尝试保留储备内存的自由假如’to-space’被渴望得到。 
    提前启动标记周期
    使用-XX:ConcGCThreads=n选项增大标记线程的数量
G1垃圾收集器开关完整列表

这是一个G1垃圾收集器开关的完整列表,记着去使用上述的最佳实践。

选项和默认值描述
-XX:+UseG1GC使用垃圾优先(G1,Garbage First)收集器
-XX:MaxGCPauseMillis=n设置垃圾收集暂停时间最大值指标。这是一个软目标,Java虚拟机将尽最大努力实现它
-XX:InitiatingHeapOccupancyPercent=n触发并发垃圾收集周期的整个堆空间的占用比例。它被垃圾收集使用,用来触发并发垃圾收集周期,基于整个堆的占用情况,不只是一个代上(比如:G1)。0值 表示’do constant GC cycles’。默认是45
-XX:NewRatio=n年轻代与年老代的大小比例,默认值是2
-XX:SurvivorRatio=neden与survivor空间的大小比例,默认值8
-XX:MaxTenuringThreshold=n最大晋升阈值,默认值15
-XX:ParallerGCThreads=n设置垃圾收集器并行阶段的线程数量。默认值根据Java虚拟机运行的平台有所变化
-XX:ConcGCThreads=n并发垃圾收集器使用的线程数量,默认值根据Java虚拟机运行的平台有所变化
-XX:G1ReservePercent=n为了降低晋升失败机率设置一个假的堆的储备空间的上限大小,默认值是10
-XX:G1HeapRegionSize=n使用G1收集器,Java堆被细分成一致大小的区域。这设置个体的细分的大小。这个参数的默认值由工学意义上的基于堆的大小决定

G1收集器的垃圾收集日志


G1收集器的垃圾收集日志

我们需要涵盖的最后的主题是使用G1垃圾回收器的日志记录信息来分析性能。这部分提供你可以用来收集打印在日志里的数据和信息的开关的快速的概览。

设置日志详情

你可以设置三种不同级别的详情。 
(1)-verbosegc (和**-XX:+PrintGC**等效)参数设置fine日志详情级别 
sample Output
[GC pause (G1 Humongous Allocation) (young) (initial-mark) 24M- >21M(64M), 0.2349730 secs] 
[GC pause (G1 Evacuation Pause) (mixed) 66M->21M(236M), 0.1625268 secs]

(2)-XX:PrintGCDetails设置finer详情级别。使用这个选项会显示以下信息: 
* 显示每个阶段的平均、最小、最大时间 
* 根扫描、RSet更新(附带处理的缓冲信息)、RSet扫描、对象复制、终止(附带尝试次数)。 
* 也显示’other’时间,比如花费在选择CSet上的时间、引用处理、引用排队和释放CSet。 
* 显示Eden、Survivor和总堆空间占用。 
Sample Output
[Ext Root Scanning (ms): Avg: 1.7 Min: 0.0 Max: 3.7 Diff: 3.7] 
[Eden: 818M(818M)->0B(714M) Survivors: 0B->104M Heap: 836M(4096M)->409M(4096M)]

(3)-XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest设置finest详情级别。类似finer但是包括每个工作者线程的信息。 
Sample Output
[Ext Root Scanning (ms): 2.1 2.4 2.0 0.0 
Avg: 1.6 Min: 0.0 Max: 2.4 Diff: 2.3] 
[Update RS (ms): 0.4 0.2 0.4 0.0 
Avg: 0.2 Min: 0.0 Max: 0.4 Diff: 0.4] 
[Processed Buffers : 5 1 10 0 
Sum: 16, Avg: 4, Min: 0, Max: 10, Diff: 10]

决定时间显示格式

有两个开关可以决写在垃圾收集日志中如何显示时间。 
(1)-XX:+PrintGCTimeStamps - 显示自从Java虚拟机启动之后流逝的时间。 
Sample Output
1.729: [GC pause (young) 46M->35M(1332M), 0.0310029 secs]

(2)-XX:+PrintGCDateStamps - 为每一项添加日期时间的前缀。 
Sample Output
2012-05-02T11:16:32.057+0200: [GC pause (young) 46M->35M(1332M), 0.0317225 secs]

理解G1日志

为了理解这个日志,这部分使用实际的垃圾收集输出日志来明确一些术语。下面的示例列出了输出日志中的术语和值,你会在日志中找到它们。 
**注意:**更多信息请查看Poonam Bajaj’s Blog post on G1 GC logs

G1日志术语索引

Worker Start 
Parallel Time 
External Root Scanning 
Update Remembered Set 
Scanning Remembered Sets 
Object Copy 
Termination Time 
GC Worker End 
GC Worker Other 
Clear CT 
Other 
CSet 
Ref Proc 
Ref Eng 
Free CSet 
此入门教程源地址上有一些介绍,但是和Poonam Bajaj’s Blog post on G1 GC logs内容几乎相同,所以详细信息请看我翻译的Poonam Bajaj’s Blog post on G1 GC logs

资源

更多相关信息请查看下面这些站点和链接:

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
G1(Garbage First)垃圾回收是一种低延迟的垃圾回收,它可以在不影响应用程序吞吐量的情况下,有效地处理大量的内存垃圾。下面是G1垃圾回收的执行流程: 1. 初始标记(Initial Mark):该阶段的目标是标记所有的根对象,并且标记从根对象直接可达的对象。为了达到这个目的,G1垃圾回收会扫描所有的Java线程的栈,以及记录下所有的GC Root。 2. 并发标记(Concurrent Mark):在初始标记之后,G1垃圾回收会开始并发的标记所有从根对象可达的对象。这是一个并发的过程,不会阻塞应用程序的执行。 3. 最终标记(Final Mark):在并发标记之后,G1垃圾回收会再次暂停应用程序的执行,以完成所有未被标记的存活对象的标记。这个过程与初始标记是类似的。 4. 筛选回收(Live Data Counting and Evacuation):在最终标记之后,G1垃圾回收会计算每个区域中存活的数据量。然后,它会选定一些区域作为回收集(Collection Set),将这些区域中的存活对象复制到空闲的区域中,并将这些区域标记为可回收的。 5. 清除(Cleanup):在筛选回收之后,G1垃圾回收会开始清理所有被标记为可回收的区域。 需要注意的是,G1垃圾回收是一个全局垃圾回收,因此它不仅仅会处理单个堆区域的垃圾回收,而是会处理整个Java堆。同时,它还会根据应用程序运行的情况,动态地调整回收集的大小,以达到最佳垃圾回收效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值