java中的G1回收器怎么用

发布时间:2021-09-27 17:50 来源:亿速云 阅读:0 作者:柒染 栏目: 开发技术

这篇文章给大家介绍java中的G1回收器怎么用,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

G1 回收器

G1回收器是在jdk1.7中正式使用的全新垃圾回收器,并且是jdk9及之后版本的默认回收器。从分代上看,G1 依然属于分代垃圾回收器,它会区分年轻代和老年代,依然有eden区和survivor区,但从堆的结构上看,它并不要求整个eden区、年轻代或者老年代都连续。G1使用了全新的分区算法,特点如下:

  • 并行性:G1在回收期间,可以由多个GC线程同时工作,有效利用多核计算能力。

  • 并发性:G1在拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,一般来说,不会在整个回收期间完全阻塞应用程序。

  • 分代GC:G1依然是一个分代回收器,但是和之前的回收器不同,它同时兼顾年轻代和老年代,其他回收器或者工作在年轻代,或者工作在老年代。

  • 空间整理:G1在回收过程中,会进行适当的对象移动,不像CMS,只是简单地标记清理对象,在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效地复制对象,减少碎片空间。

  • 可预见性:由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,全局停顿也能得到较好的控制。

1 G1的内存划分和主要收集过程

G1将堆进行分区,划分为一个个的区域,每次回收时,只回收其中几个区域,以此来控制垃圾回收产生的一次停顿时间。

G1的回收过程可能有4个阶段:

  • 新生代GC

  • 并发标记周期

  • 混合回收

  • 如果需要,可能会进行FullGC

2. G1的新生代GC

新生代GC的主要工作是回收eden区和survivor区。一旦eden区被占满,新生代GC就会启动。新生代GC只处理eden区和survivor区,回收后所有的eden区都应该被清空,而survivor区会被回收一部分数据

3. G1 的并发标记周期

G1 的并发阶段和 CMS 有点类似,它们都是为了降低一次停顿时间,而将可以和应用程序并发的部分单独提取出来执行。

并发标记周期可以分为以下几步:

  • 初始标记:标记从根节点直接可达的对象。这个阶段会伴随一次新生代GC,它是产生全局停顿的,应用程序线程在这个阶段必须停止执行。

  • 根区域扫描:由于初始标记必然会伴随一次新生代GC,所以在初始化标记后,eden区被清空,并且存活对象被移入survivor区。在这个阶段,将扫描由survivor区直接可达的老年代区域,并标记这些直接可达的对象。这个过程是可以和应用程序并发执行的,但是根区域扫描不能和新生代GC同时执行,因为如果恰巧在此时需要进行新生代GC,就需要等待根区域扫描结束后才能进行。如果发生这种情况,这次新生代GC的时间就会延长。

  • 并发标记:和CMS类似,并发标记将会扫描并查找整个堆的存活对象,并做好标记。这是一个并发的过程,并且这个过程可以被一次新生代GC打断。

  • 重新标记:和CMS一样,重新标记也是会产生应用程序停顿的。由于并发标记过程中,应用程序依然在运行,因此标记结果可能需要修正,所以在此对上一次的标记结果进行补充。在 G1 中,这个过程使用SATB(Snapshot-At-The-Beginning)算法完成,即G1会在标记之初为存活对象创建一个快照,这个快照有助于加速重新标记的速度。

  • 独占清理:这个阶段是会引起停顿的。它将计算各个区域的存活对象和GC回收比例,并进行排序,识别可供混合回收的区域。识别可供混合回收的区域。在这个阶段,还会更新记忆集(Remebered Set)。该阶段给出了需要被混合回收的区域并进行了标记,在混合回收阶段需要这些信息。

  • 并发清理:这里会识别并清理完全空闲的区域。它是并发的清理,不会引起停顿。

除了初始标记重新标记独占清理,其他几个阶段都可以和应用程序并发执行。

在并发标记周期中,G1会产生如下日志:

3.1 初始标记,它伴随着一次新生代GC
[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0014636 secs]
   ...
   [Eden: 2048.0K(14.0M)->0.0B(13.0M) Survivors: 0.0B->1024.0K Heap: 13.8M(40.0M)->2624.1K(40.0M)]
 [Times: user=0.01 sys=0.00, real=0.00 secs]
3.2 一次并发的根区域扫描,并发扫描过程中不能被新生代GC中断。
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0003832 secs]
3.3 并发标记,并发标记可以被新生代GC打断,下面的日志显示了一次并发标记被3次新生代GC打断。
[GC concurrent-mark-start]
[GC pause (young), 0.0003382 secs]
...
  [Eden: 2048.0K(14.0M)->0.0B(13.0M) Survivors: 0.0B->1024.0K Heap: 13.8M(40.0M)->2624.1K(40.0M)]
[Times: user=0.01 sys=0.00, real=0.00 secs] 
...
  [Eden: 2048.0K(14.0M)->0.0B(13.0M) Survivors: 0.0B->1024.0K Heap: 13.8M(40.0M)->2624.1K(40.0M)]
[Times: user=0.01 sys=0.00, real=0.00 secs] 
...
  [Eden: 2048.0K(14.0M)->0.0B(13.0M) Survivors: 0.0B->1024.0K Heap: 13.8M(40.0M)->2624.1K(40.0M)]
[Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC concurrent-mark-end, 0.0003929 secs]
3.4 重新标记:是会引起全局停顿的,它的日志如下:
[GC remark [Finalize Marking, 0.0006027 secs] [GC ref-proc, 0.0000295 secs] [Unloading, 0.0003390 secs], 0.0010737 secs]
 [Times: user=0.01 sys=0.00, real=0.01 secs]
3.5 重新标记后会进行独占清理,独占清理会重新计算各个区域的存活对象,并以此可以得到每个区域进行GC的效果,它的日志如下:
[GC cleanup 10M->10M(40M), 0.0003016 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs]
3.6 并发清理,是并发执行的,它会根据独占清理阶段计算得出每个区域的存活对象数量,直接回收不包含存活对象的区域。它的日志如下:
[GC concurrent-cleanup-start]
[GC concurrent-cleanup-end, 0.0000016 secs]

4. 混合回收

在并发标记周期中,虽然有部分对象被回收,但是总体上说,回收的比例是相当低的。但是在并发标记周期后,G1已经明确知道哪些区域含有比较多的垃圾对象,在混合回收阶段就可以专门针对这些区域进行回收。这个阶段叫做混合回收,是因为这个阶段既会执行正常的年轻代GC,又会选取一些被标记的老年代区域进行回收,它同时处理了新生代和老年代

混合GC会产生如下日志:

[GC pause (mixed), 0.0003382 secs]
...
  [Eden: 2048.0K(14.0M)->0.0B(13.0M) Survivors: 0.0B->1024.0K Heap: 13.8M(40.0M)->2624.1K(40.0M)]
[Times: user=0.01 sys=0.00, real=0.00 secs]

混合GC会执行多次,走到回收了足够多的内存空间,然后它会触发一次新生代GC。新生代GC后,又可难会发生一次并发标记周期的处理,最后又会引起混合GC的执行。

5. 必要时的Full GC

和CMS类似,并发回收由于让应用程序和GC线程交替工作,总是不能完全避免在特别繁忙的场合出现在回收过程中内存不充足的情况。当遇到这种情况时,G1也会转入一个FullGC。

当G1在并发标记时,由于老年代被快速填充,G1会终止并发标记而转入一个Full GC:

[GC concurrent-mark-start]
[Full GC 898->896(900M), 0.7003382 secs]
  [Eden: 0K(45.0M)->0.0B(45.0M) Survivors: 0.0B->0B Heap: 898.7M(900.0M)->896.2M(900.0M)]
[Times: user=1.01 sys=0.00, real=0.075 secs] 
[GC concurrent-mark-abort]

此外,如果在混合GC时空间不足,或者在新生代GC时survivor区和老年代无法容纳幸存对象,都会导致一次FullGC.

6. G1的日志

# 表示应用程序发生了一次新生代GC,这是在初始标记时发生的,耗时0.0018193秒,意味着程序至少暂停了0.0018193秒
[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0018193 secs]
   # 后续并行时间,表示所有GC线程总的花费时间,这里为0.9毫秒,workers为8表示有8个GC线程
   [Parallel Time: 0.9 ms, GC Workers: 8]
      # GC线程的执行情况,这里统计了8个线程的统计值,如 平均、最小、最大和差值(最大值与最小值之差),
      # 106.6 表示在应用程序启动 106.6 毫秒后,启动了该GC线程
      [GC Worker Start (ms): Min: 106.6, Avg: 106.7, Max: 106.7, Diff: 0.1]
      # 根扫描时间的统计值
      [Ext Root Scanning (ms): Min: 0.4, Avg: 0.4, Max: 0.5, Diff: 0.1, Sum: 3.3]
      # 更新记忆集(Remember Set)的耗时。
      # 记忆集是G1中维护的一个数据结构,简称RS。每一个G1区域都有一个RS与之关联。由于G1回收时是按照区域
      # 回收的,比如在回收区域A的对象时,很可能并不回收区域B的对象,为了回收区域A的对象,要扫描区域B甚
      # 至整个堆来判定区域A中哪些对象不可达,这样做的代价显然很大。因此,G1在区域A的RS中,记录了在区域
      # A中被其他区域引用的对象,这样在回收区域A时,只要将RS视为区域A根集的一部分即可,从而避免做整个堆
      # 的的扫描。由于系统在运行过程中,对象之间的引用关系是可能时刻变化的,为了更高效地跟踪这些引用关系,
      # 会将这些变化记录在Update Buffers中。这里的Processed Buffers指的就是处理这个Update Buffers数据。
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      # 扫描RS的时间 
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      # 在正式回收前,G1 会对被回收区域的对象进行疏散,即将存活对象放置在其他区域中,因此需要进行对象复制
      [Object Copy (ms): Min: 0.2, Avg: 0.3, Max: 0.4, Diff: 0.2, Sum: 2.2]
      # 给出GC工程线程终止的信息,这里的终止时间是线程花在终止阶段的耗时。在GC线程终止前,它们会检查其
      # 他GC线程的工作队列,查看是否仍然还有对象引用没有处理完,如果其他线程仍然有没有处理完的数据,请求
      # 终止的线程会帮助它尽快完成,随后再尝试终止。其中,Termination Attempts 展示了工作线程的终止次数。
      [Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 1.0]
         [Termination Attempts: Min: 1, Avg: 2.9, Max: 6, Diff: 5, Sum: 23]
      # 显示GC线程花费在其他任务中的耗时
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms): Min: 0.8, Avg: 0.8, Max: 0.9, Diff: 0.1, Sum: 6.6]
      # GC 工作线程的完成时间
      [GC Worker End (ms): Min: 107.5, Avg: 107.5, Max: 107.5, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   # 显示清空 CardTable的时间,RS 就是依靠CardTable来记录哪些是存活对象的
   [Clear CT: 0.1 ms]
   # 显示其他几个任务的耗时,比如选择CSet(Collection Sets,表示被选取的、将要被回收的区域的集合)的时
   # 间、Ref Proc(处理弱引用、软引用的时间)、Ref End(弱引用、软引用入队时间)和Free CSet(释放被
   # 回收的CSet中区域的时间,包括它们的RS)
   [Other: 0.8 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.7 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 2048.0K(14.0M)->0.0B(13.0M) Survivors: 0.0B->1024.0K Heap: 13.8M(40.0M)->2656.1K(40.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs]

7. 相关的参数

  • -XX:+UseG1GC:启用G1回收器

  • -XX:MaxGCPauseMillis: 用于指定目标最大停顿时间。如果任何一次停顿时间超过这个设置值,G1就会尝试调整新生代和老年代的比例、调整堆大小、调整晋升年龄等,试图达到预设目标。对于性能调优来说,总是鱼和熊掌不可兼得,如果停顿时间缩短,对于新生代来说,意味着很可能要增加新生代GC的次数。对于老年代来说,为了获得短暂的停顿时间,在混合GC时,一次收集的区域数量也会变少,这样无疑增加了进行FullGC的可能性。

  • -XX:ParallelGCThreads: 设置并行回收时GC的工作线程数量。

  • -XX:InitatingHeapOccupancyPercent: 指定当整个堆使用率达到多少时,触发并发标记周期的执行,默认值是45.InitatingHeapOccupancyPercent一旦设置,始终都不会被G1修改,这意味着G1不会试图改变这个值来满足MaxGCPauseMillis 的目标,那么引起FullGC的可能性也大大增加,反之,一个过小的InitatingHeapOccupancyPercent值会使得并发标记周期执行非常频繁,大量GC线程抢占CPU,导致应用程序的性能有所下降。

免责声明:本站发布的内容(图片、视频和文字)以原创、来自互联网转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系QQ:712375056 进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。