导语
  G1回收器是在JDK1.7中正式使用的一种全新的垃圾回收器,它的目标是为了取代CMS回收器。G1回收器拥有独特的垃圾回收策略,和之前的任意的一种垃圾回收器都有所不同,但是从分代策略上来说依然是属于分代垃圾回收器,也分为年轻代和老年代,任然是有eden区和survivor区。但从堆结构上看,它并不要求整个的eden区、年轻代或者老年代是连续的空间。它所使用的是一个分区算法,这个后面的分享中会介绍到。

文章目录

    • G1特点
    • G1垃圾收集器参数
    • G1 内存划分和主要收集过程
    • G1 的新生代GC
      • 日志分析
    • G1 的并发标记周期
      • 标记周期日志分析
    • 混合回收
      • G1 的 Full GC操作
  • 总结

G1特点

  作为CMS的替代算法,G1使用了全新的分区算法特点如下

并行性 :G1在回收期间,可以由多个GC线程同时工作,有效的利用多核的计算能力
并发性 :G1拥有与应用程序交替执行的能力,部分工作可以与应用程序同时执行,所以,不会在一整个回收期间完全阻塞应用程序。
分代GC:G1 是一个分代收集器,与之前的收集器不同的是它兼顾了年轻代和老年代,相比较于其他的垃圾回收器,其他的收集器只能工作于年轻代或者老年代其中的一个,所以在这个方面和之前的收集器有所不同。
空间整理:G1 在回收过程中会进行适当的对象移动,与CMS不同,只是简单的标记清理对象,在若干次GC之后,CMS必须执行一次碎片整理,G1则是每次回收都会有效的复制对象,尽量减少空间碎片。
可预见性:由于分区的原因,G1可以只选取部分区域进行内存回收,可以缩小回收范围,对于全局停顿有很好的掌控性。

G1垃圾收集器参数

选项/默认值 说明
-XX:+UseG1GC 使用 G1 (Garbage First) 垃圾收集器
-XX:MaxGCPauseMillis=n 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标.
-XX:InitiatingHeapOccupancyPercent=n 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45.
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:G1HeapRegionSize=n 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.

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

  G1收集器将堆进行分区,将内存分为不同的区域,每次回收的时候,只回收其中的几个区域,这样可以有效的来控制垃圾回收产生的一次停顿时间。

  G1 的收集过程有以下的4个阶段

  • 新生代GC
  • 并发标记周期
  • 混合收集
  • Full GC

G1 的新生代GC

  新生代的GC主要工作是回收eden区和survivor区。一旦eden区被占用满,新生代GC就会启动。如下图所示

  其中E表示eden区,S表示survivor区,O表示老年代。可以看出来在新生代GC只处理eden区和survivor区,回收之后所有的Eden区都应该是被清空,survivor去被回收部分数据,但至少会存在一个survivor区。与其他的收集器来讲这点的变化不会太大。另外的变化就是老年代的区域会增加,这是因为部分survivor区或者是eden区的对象可能会升级到老年代。

日志分析

-Xmx1g -Xms1g -Xmn900m -XX:+UseG1GC -Xloggc:gc.log -XX:+PrintGCDetails

观察日志情况会看到与之前的垃圾回收日志信息有所不同


10.519: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0359476 secs][Parallel Time: 34.8 ms, GC Workers: 13][GC Worker Start (ms): Min: 10519.2, Avg: 10519.3, Max: 10519.4, Diff: 0.2][Ext Root Scanning (ms): Min: 0.0, Avg: 0.2, Max: 0.4, Diff: 0.4, Sum: 2.8][Update RS (ms): Min: 1.2, Avg: 1.3, Max: 1.5, Diff: 0.4, Sum: 17.0][Processed Buffers: Min: 2, Avg: 2.1, Max: 3, Diff: 1, Sum: 27][Scan RS (ms): Min: 3.6, Avg: 3.7, Max: 3.8, Diff: 0.2, Sum: 48.1][Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Object Copy (ms): Min: 29.3, Avg: 29.3, Max: 29.4, Diff: 0.1, Sum: 381.2][Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Termination Attempts: Min: 1, Avg: 1.9, Max: 3, Diff: 2, Sum: 25][GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.3][GC Worker Total (ms): Min: 34.5, Avg: 34.6, Max: 34.7, Diff: 0.2, Sum: 449.4][GC Worker End (ms): Min: 10553.8, Avg: 10553.8, Max: 10553.9, Diff: 0.1][Code Root Fixup: 0.0 ms][Code Root Purge: 0.0 ms][Clear CT: 0.2 ms][Other: 0.9 ms][Choose CSet: 0.0 ms][Ref Proc: 0.2 ms][Ref Enq: 0.0 ms][Redirty Cards: 0.2 ms][Humongous Register: 0.0 ms][Humongous Reclaim: 0.0 ms][Free CSet: 0.3 ms][Eden: 72.0M(69.0M)->0.0B(107.0M) Survivors: 8192.0K->10.0M Heap: 458.0M(800.0M)->461.5M(800.0M)][Times: user=0.17 sys=0.19, real=0.04 secs]
10.555: [GC concurrent-root-region-scan-start]
10.556: [GC concurrent-root-region-scan-end, 0.0010221 secs]
10.556: [GC concurrent-mark-start]
10.655: [GC concurrent-mark-end, 0.0992927 secs]
10.656: [GC remark 10.656: [Finalize Marking, 0.0012177 secs] 10.657: [GC ref-proc, 0.0000598 secs] 10.657: [Unloading, 0.0004519 secs], 0.0036237 secs][Times: user=0.02 sys=0.00, real=0.00 secs]
10.659: [GC cleanup 466M->462M(800M), 0.0013445 secs][Times: user=0.01 sys=0.00, real=0.00 secs]
10.661: [GC concurrent-cleanup-start]
10.661: [GC concurrent-cleanup-end, 0.0000085 secs]

  和其他收集器的日志相比G1的日志内容比较丰富,从日志中可以看到,Eden区的使用情况,以及Survivors的使用情况。

[Eden: 900.0M(900.0M)->0.0B(796.0M) Survivors: 0.0B->104.0M Heap: 915.5M(1024.0M)->452.5M(1024.0M)]

G1 的并发标记周期

  G1并发阶段与CMS类似,都是为了降低一次的停顿时间,可以将与应用程序一起执行的部分提取然后执行。主要可以分为如下的步骤

  • 初始标记 :标记从根节点直接可以达到的对象,这个阶段可以从日志中看到,会伴随一次新生代的GC,会产生全局停顿,应用程序在这个阶段必须停顿。
  • 根区域扫描 :由于初始标记必然会伴随一次新生代GC,所以在初始化标记之后,eden被清空,并且存活对象被移入survivor区。这个阶段中,将扫描survivor区直接可达的老年区域,并且标记这些直接可达的对象。而这个过程可以和应用程序并发执行。但是根区域扫描不能和新生代GC同时执行,所以如果在这个时候需要进行新生代GC,GC就需要等待根区域扫描结束之后才能进行,如果发生这种情况,新生代的GC时间就会延长。
  • 并发标记 :和CMS类似,并发标记将会扫描整个堆的存活对象,并做好标记。这个过程是并发的,并且这个过程可以被一次新生代GC打断。
  • 重新标记 与CMS一样,重新标记会产生应用程序的停顿,由于在并发标记的过程中应用程序依然在运行,所以标记结果可能需要从新修正,所以在对上次标记的结果进行补充,在G1中,这个整个的过程是通过SATB(Snapshot-At-The-Beginning)算法来完成的。也就是G1会在标记之前给存活的对象创建一个快照,这个快照有助于加速重新标记的速度。
  • 独占清理 在这个阶段会引起程序的停顿,计算各个区域的存活对象和GC的回收比例进行排序,识别可以提供混合回收的区域,这个阶段还会更新记忆集(Remebered Set),给出需要利用混合回收的区域并标记。
  • 并发清理阶段 识别清理完全空闲的区域,由于是并发清理不会引起停顿。

并发标记周期前

  如图所示,在并发标记周期前堆内存使用情况,由于并发标记周期包含一次新生代的GC,所以新生代会进行整理,但由于并发标记执行时,程序依然处于运行期,所以并发标记之后又有新的eden空间被使用。
并发标记周期后

  并发标记周期执行前后最大的不同是在新生代GC之后,系统会增加一些标记为G的区域,这些区域被标记,因为它们内部的垃圾比例较高,所以在之后的混合GC中进行收集(在并发标记周期中并没有正式回收这些区域)。这些将要被回收的区域会被G1记录在Collection Sets的集合中,在之前的日志中可以看到。

标记周期日志分析

1、初始标记 伴随一次新生代的GC

10.519: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0359476 secs][Eden: 72.0M(69.0M)->0.0B(107.0M) Survivors: 8192.0K->10.0M Heap: 458.0M(800.0M)->461.5M(800.0M)]

  可以看到在初始化,eden区域被清空。

2、并发扫描根区域

10.555: [GC concurrent-root-region-scan-start]
10.556: [GC concurrent-root-region-scan-end, 0.0010221 secs]

3、并发标记


10.556: [GC concurrent-mark-start]
10.655: [GC concurrent-mark-end, 0.0992927 secs]

4、重新标记
  会引起全局停顿


10.656: [GC remark 10.656: [Finalize Marking, 0.0012177 secs] 10.657: [GC ref-proc, 0.0000598 secs] 10.657: [Unloading, 0.0004519 secs], 0.0036237 secs]

5、独占清理
  独占清理会重新计算各个区域的存活对象,并以此可以得到每个区域进行回收的比例。


10.659: [GC cleanup 466M->462M(800M), 0.0013445 secs][Times: user=0.01 sys=0.00, real=0.00 secs] 

6、并发清理
  根据独占清理阶段计算得出的每个区域的存活对象数量,直接回收已经不包含存活对象的区域

10.661: [GC concurrent-cleanup-start]
10.661: [GC concurrent-cleanup-end, 0.0000085 secs]

测试代码

public class StopWorldTest {public static class MyThread extends Thread{HashMap map = new HashMap();@Overridepublic void run() {try{while (true){if (map.size()*512/1024/1024>=500){map.clear();System.out.println("clean map");}byte[] b1 ;for (int i =0;i<100;i++){b1 = new byte[512];map.put(System.nanoTime(),b1);}Thread.sleep(1);}} catch (InterruptedException e) {e.printStackTrace();}}}public static class PrintThread extends Thread{public static final long starttime = System.currentTimeMillis();@Overridepublic void run() {try {while (true){long t = System.currentTimeMillis()-starttime;System.out.println(t/1000+"."+t%1000);Thread.sleep(100);}} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {MyThread t = new MyThread();PrintThread p = new PrintThread();t.setName("MyThread");p.setName("PrintThread");t.start();p.start();}
}

虚拟机参数

-Xmx800m -Xms800m -XX:+UseG1GC -Xloggc:gc.log -XX:+PrintGCDetails

混合回收

  在上面的并发标记周期中,虽然是有一部分对象被回收,但是从总体上来讲,回收的比率是比较低的。并发标记带来的最大的好处就是G1可以明确知道哪些区域所包含的垃圾较多,在混合回收的时候就可以专门对这些区域进行回收。当然G1会优先回收垃圾比例较高的区域,因为这些区域的回收性价比比较高。这就G1( Garbage First Garbage Collector)
  这个整合的阶段被称为是混合回收,因为在这个阶段会执行正常的年轻代GC,又选择性的回收一部分被标记的老年代,同时处理老年代与新生代。如图所示混合回收前。

混合回收之后

  会看到由于新生代GC毕然导致Eden被清空,而标记G的区域就变为了垃圾比例最高的区域,所以被清理。


17.698: [GC pause (G1 Evacuation Pause) (mixed), 0.0119168 secs][Eden: 34.0M(34.0M)->0.0B(259.0M) Survivors: 6144.0K->5120.0K Heap: 208.0M(800.0M)->201.0M(800.0M)]

  混合GC会执行多次,知道回收了足够多的内存,然后就会触发一次新生代GC,新生代GC之后有可能会执行一次并发标记周期,最后又会引起混合GC。

G1 的 Full GC操作

  在有些场景下会在日志里面出现了Full GC 的信息。这是因为并发收集由于让应用程序和GC线程交替工作,所以总是不能避免在特别繁忙的时候回出现回收的过程中内存不足的情况,这个时候就会进行一次的Full GC,可以使用如下的参数作为测试参数查看对应的效果

-Xmx1g -Xms1g -Xmn900m -XX:+UseG1GC -Xloggc:gc.log -XX:+PrintGCDetails

总结

  对于G1回收器,可以通过-XX:+UseG1GC 标记来打开,可以通过-XX:+MaxGCPauseMillis 用于指定目标最大停顿时间,如果任何一次的停顿大小与这个时间G1就会尝试重新调整新生代和老年的比例、调整整个堆大小、调整晋升年龄等。通过-XX:+ParallelGCThreads 设置并行回收的时候,GC工作线程数量,-XX:InitiatingHeapOccupancyPercent参数可以设置整个堆使用率达到多少的时候触发标记周期执行,默认是45。整个值一旦被设置就不会被修改,这个值配合MaxGCPauseMillis 来使用。当然还有很多的性能调优的参数,这里就不再一一介绍。

JVM优化系列-JVM G1 垃圾收集器相关推荐

  1. JVM优化系列-JVM垃圾收集器介绍

    导语   既然是串行顾名思义就是使用单线程的方式进行执行,每次执行回收的时候,串行回收器只有一个工作线程,这样对于并行能力较弱的计算机,串行回收器更多的独占线程专一执行的方面有着良好的实现,也就是说在 ...

  2. JVM由浅入深系列——详解垃圾收集器与内存分配策略

    文章目录 一.内存分配策略 1.引用计数算法 2.可达性分析算法 3.标记清除算法 4.标记复制算法 5.标记整理算法 二.收集器 1.Serial收集器 2.ParNew收集器 3.Parallel ...

  3. JVM优化系列-JVM内存溢出的原因

    导语   内存溢出(OutOfMemory)OOM,通常情况下出现在某一块内存空间快要消耗完的时候.在Java程序中,导致内存溢出的原因有很多,下面就来分享关于内存溢出的一些问题.其中包括堆内存.直接 ...

  4. JVM性能调优实践:G1 垃圾收集器介绍篇

    前言 前面两篇主要整理了性能测试的主要观察指标信息:性能测试篇,以及JVM性能调优的工具:JVM篇.这一篇先简单总结一下GC的种类,然后侧重总结下G1(Garbage-First)垃圾收集器的分代,结 ...

  5. 详解 JVM Garbage First(G1) 垃圾收集器

    前言 Garbage First(G1)是垃圾收集领域的最新成果,同时也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命.如果使用Java 8/9,那么有很大可能希望对G1收集器进行 ...

  6. JVM性能调优实践——G1 垃圾收集器分析、调优篇

    前言 关于G1 GC以及其他垃圾收集器的介绍可以参考前一篇JVM性能调优实践--G1 垃圾收集器介绍篇.了解了G1垃圾收集器的运行机制之后,就可以针对一些GC相关参数来调整内存分配以及运行策略.下文的 ...

  7. JVM垃圾回收——G1垃圾收集器

    目录 一.什么是G1垃圾收集器 二.G1垃圾收集器的内存划分 三.G1垃圾收集器的收集过程 四.G1收集器的优缺点 五.G1收集器的JVM参数配置 一.什么是G1垃圾收集器 Garbage First ...

  8. JVM垃圾回收器-G1垃圾收集器

    Java8的G1垃圾回收器官方文档参考:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#ga ...

  9. java -g_【JVM】7、深入理解Java G1垃圾收集器

    本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么 ...

最新文章

  1. Java基础。public,private,static变量!以及一个实例化的小例子 以及方法
  2. 《算法竞赛入门经典》习题4-3 黑白棋(Othello, ACM、ICPC World Finals 1992, UVa220)
  3. TUM数据集制作BundleFusion数据集
  4. redis storm mysql_storm-redis 详解
  5. 用Python发一封图文并茂的邮件 荐
  6. python零基础8分钟基础入门
  7. 南工院C语言试卷答案,南工院11-12-1C期末B试卷 附答案.doc
  8. 第2.01节 发布版本vs源码编译
  9. node环境配置安装(nvm)
  10. cookie和session机制之间的区别与联系
  11. EnableQ,细腻让其与众不同
  12. 使用Hutool发送工作日报
  13. 如何根据公司名称来筛选快递
  14. 跳转到app下载页面和app评论页面
  15. ZT I Believe I Can Fly(我相信我能飞)
  16. 【目标检测】YOLOv5分离检测和识别
  17. 项目生命周期管理-瀑布模型
  18. Counterfeit Dollar(找硬币)
  19. mysql数据库语言_mysql数据库sql语句基础知识
  20. [android开发必备]Android开发者社区汇总

热门文章

  1. java 继承 extends_java中的继承 (extends) 详解
  2. 阿尔伯塔大学的计算机科学专业好吗,去阿尔伯塔大学留学这些专业千万不能错过!...
  3. (转)CString工作原理和常见问题分析
  4. 轻松搞定JSONP跨域请求
  5. Windows 下的Dig的安装及应用集合(一)
  6. winform B窗體調用A窗體的DATAGRIDVIEW刷新
  7. 批量建立EXCHANGE邮件帐号建立三部曲
  8. Linux 多用户和多用户边界
  9. HTTP电脑发送短信接口调用示例
  10. 异常处理 Exception