飞道的博客

玩转Java虚拟机(十三)

185人阅读  评论(0)

打卡学习JVM,第十三天

本人学习过程中所整理的代码,源码地址

垃圾收集器两个重要的指标

- 吞吐量

  • 吞吐量关注的是在一个指定的时间内,最大化一个应用的工作量
  • 如下方式来衡量一个系统吞吐量的好坏:
    1、在一小时内同一个事务(或者任务、请求)完成的次数(tps)
    2、数据库一小时可以完成多少次查询
  • 对于关注吞吐量的系统,卡顿是可以接受的,因为这个系统关注长时间的大量任务的执行能力,单词快速的响应并不值得考虑

- 响应能力

  • 响应能力指一个程序或者系统对请求是否能够及时响应,比如:
    1、一个桌面UI能多快地响应一个时间
    2、一个网站能够多快返回一个页面请求
    3、数据库能够多快返回查询的数据
  • 对于这类对响应能力敏感的场景,长时间的停顿是无法接受的

对于Java应用的调优,主要就集中于这两个指标

为了很好的满足这两个指标,G1垃圾收集器应运而生

G1 Garbage Collector

G1收集器是一个面向服务端的非实时垃圾收集器,适用于多核处理器、大内存容量的服务端系统,它满足短时间GC停顿的同时达到一个较高的吞吐量,JDK 7以上版本适用

- G1收集器的设计目标

  • 与应用线程同时工作,几乎不需要STW(与CMS类似)
  • 整理剩余空间,不产生内存碎片(CMS只能在Full GC时,用STW整理内存碎片)
  • GC停顿更加可控
  • 不牺牲系统的吞吐量
  • GC不要求额外的内存空间(CMS需要预留空间存储浮动垃圾)

- G1的设计规划是要替换掉CMS

  • G1在某些方面弥补了CMS的不足,比如CMS基于标记-清除算法,会产生内存碎片,而G1基于复制算法,高效的整理剩余内存,不需要管理内存碎片
  • 另外,G1提供了更多手段以达到对GC停顿时间的可控

- Hotspot虚拟机主要构成


JVM的性能主要由Heap、JIT Compiler、Garbage Collector控制,堆是对象存储的位置,这个区域主要由JVM启动时所选择的垃圾收集器所管理。大多数调优操作都是和调整堆的大小有关并且选择最适合当前形势的垃圾收集器。JIT Compiler对性能也有很大的影响,但是在JVM的新版本中很少需要对JIT Compiler进行调优

- 传统垃圾收集器堆结构


注:永久代在JDK 8后进行了移除,并用元空间进行替换

- G1收集器堆结构

  • 堆(Heap)被划分为一个个相等的不连续的内存区域(Regions),每个region都有一个分代的角色:Eden、Survivor、Old
  • 对每个角色的数量并没有强制的限定,也就是说对每种分代内存的大小,可以动态变化
  • G1最大的特点就是高效的执行回收,优先去执行那些大量对象可回收的region
  • G1使用了GC停顿可预测的模型,来满足用户设定的GC停顿时间,根据用户设定的目标时间,G1会自动地选择哪些region要清除,一次清除多少个region
  • G1从多个region中复制存活的对象,然后集中放入一个region中,同时整理、清除内存(复制收集算法 )
  • 新生代和老年代是同时被回收的

    - G1 Collector & Traditional Collector

  • 对比使用标记-清除算法的CMS,G1使用复制算法不会造成内存碎片
  • 对比Parallel Scavenge(基于复制算法)、Parallel Old收集器(基于标记-整理算法),Parallel会对整个区域做整理,从而导致GC停顿会比较长,而G1只是特定地整理几个region
  • G1并非一个实时的收集器,与parallel Scavenge一样,对GC停顿时间的设置并不绝对生效,只是G1有较高的几率保证不超过设定的GC停顿时间。与之前的GC收集器对比,G1会根据用户设定的GC停顿时间,智能评估哪几个region需要被回收从而满足用户的设定

- G1重要概念

  • 分区(Region):G1采取了不同的策略来解决并行、串行和CMS收集器的碎片、暂停时间不可控等问题——G1将整个堆分成相同大小的region,每个分区都可能是新生代或者是老年代,但是在同一时刻只能属于某个代。新生代、幸存区、老年代这些概念还存在,成为逻辑上的概念,这样方便复用之前分代框架的逻辑,在物理上不需要连续,因此带来了额外的好处——有的region内垃圾对象特别多,G1会优先回收这些region,这样可以花费较少的时间来回收这些分区的垃圾,这也是G1名字的由来,即首先收集垃圾最多的region。依然是在新生代满了的时候对整个新生代进行回收——整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小。G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩
  • 收集集合(CSet):一组可被回收的分区的集合。在CSet中存活的数据会在GC过程中被移动到另一个可用区间,CSet中的分区可以来自Eden空间、Survivor空间、或者Old空间
  • 已记忆集合(RSet):RSet记录了其他region中的对象引用本region中对象的关系,属于points-into结构(谁引用了我的对象)。RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可

    Region1和Region3中的对象都引用了Region2中的对象,因此在Region2的RSet中记录了这两个引用
  • Snapshot-At-The-Beginning(SATB):SATB是G1 GC在并发标记阶段使用的增量式的标记算法,并发标记是并发多线程的,但并发线程在同一时刻只扫描一个region

- G1相对于CMS的优势

  • G1在压缩空间方面有优势
  • G1通过将内存空间分成区域(Region)的方式避免内存碎片问题
  • Eden、Survivor、Old区不再固定,在内存使用效率上来说更灵活
  • G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间,避免应用雪崩现象
  • G1在回收内存后会马上同时做合并空闲内存的工作,而CMS默认是在STW的时候做
  • G1会在Young GC中使用,而CMS只能在老年代中使用

- G1的适合场景

  • 服务端多核CPU、JVM内存占用较大的应用
  • 应用在运行过程中会产生大量内存碎片、需要经常压缩空间
  • 想要更可控、可预期的GC停顿周期,防止高并发下应用的雪崩现象

- G1 GC模式

G1提供了两种GC模式——Young GC和Mixed GC,两种都是完全Stop The World

  • Young GC:选定所有新生代里的region,通过控制新生代的region个数,即新生代内存大小,来控制Young GC的时间开销
  • Mixed GC:选定所有新生代里的region,外加根据global concurrent marking统计得出收集收益高的若干老年代region(垃圾对象更多的老年代区域)。在用户指定的开销目标范围内尽可能选择收益高的老年代region

Mixed GC不是Full GC,它只能回收部分老年代的region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满,无法继续进行Mixed GC,就会使用serial old GC(Full GC)来收集整个GC heap,所以本质上G1是不提供Full GC的

- global concurrent marking

global concurrent marking的执行过程类似于CMS,但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节

global concurrent marking的执行过程分为四个步骤:

  • 初始标记【STW】:它标记了从GC Root开始直接可达的对象
  • 并发标记:这个阶段从GC Root开始对heap中的对象进行标记,标记线程与应用程序线程并发执行,并且收集各个region的存活对象信息
  • 重新标记【STW】:标记那些在并发标记阶段发生变化的对象,将被回收
  • 清理:清除空region(没有存活对象),加入到free list

初始标记是共用了Young GC的暂停,只是因为它们可以复用root scan操作,所以说global concurrent marking是伴随Young GC而发生的,清理只是回收了没有存活对象的region,所以它不需要STW

- G1在运行过程中的主要模式

  • YGC(不同于CMS):在Eden充满时触发,在回收之后所有之前属于Eden的区域全部变成空白,即不属于任何一个分区(Eden、Survivor、Old)
  • 并发阶段
  • 混合模式
  • Full GC(一般是G1出现问题时发生)

- 什么时候发生Mixed GC?

由一些参数控制,另外也控制着哪些老年代region会被纳入到CSet(收集集合)

  • G1HeapWastePercent:在global concurrent marking结束之后,我们可以知道老年代区域中有多少空间要被回收,在每次Young GC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC
  • G1MixedGCLiveThresholdPercent:老年代区域中的存活对象的占比小于此参数,也就是老年代区域中垃圾对象的占比超过此参数时,才会被选入CSet
  • G1MixedGCCountTarget:一次global concurrent marking之后,最后执行Mixed GC的次数
  • G1OldCSetRegionThresholdPercent:一次Mixed GC中能被纳入到CSet的最多老年代region的数量

- G1收集概览

  • G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了

- Humongous区域

  • 在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个Humongous区装不下一个巨型对象,那么G1会寻找连续的Humongous分区来存储。为了能找到连续的H区,有时候不得不启动Full GC

- G1 Young GC

  • Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中最终Eden空间的数据为空,GC停止工作,应用线程继续执行(前面整个过程都是STW的)

  • 这时,我们需要考虑一个问题,如果仅仅GC新生代对象,我们如何找到所有的根对象呢? 老年代的所有对象都是根么?那这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念,它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用

    在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代

  • 但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了

  • 于是G1中使用point-into来解决。point-into的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描

  • 由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可

  • 需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址

  • 默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,Key是别的Region(即引用当前region的region)的起始地址,Value是一个集合,里面的元素是Card Table的Index

  • Young GC 可以分为如下5个阶段:
    阶段1:根扫描——静态和本地对象被扫描
    阶段2:更新RS——处理dirty card队列更新RS
    阶段3:处理RS——检测从年轻代指向年老代的对象
    阶段4:对象拷贝——拷贝存活的对象到survivor/old区域
    阶段5:处理引用队列——软引用,弱引用,虚引用处理

- 再谈G1 Mixed GC

Mixed GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区

它的GC步骤分为两步:

  • 全部并发标记(global concurrent marking)
  • 拷贝存活对象(evacuation)

在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为四个步骤,具体步骤见上文

三色标记算法

提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有用的方法,利用它可以推演回收器的正确性。

我们将对象分成三种类型的:

  • 黑色:根对象,或者该对象与它的子对象都被扫描(对象被标记了,且它的所有field【如成员变量等】也被标记完了)
  • 灰色:对象本身被扫描,但还没扫描完该对象中的子对象(它的field还没有被标记或标记完)
  • 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象(对象没有被标记到)

- 三色标记算法详解

  • 根对象被置为黑色,子对象被置为灰色
  • 继续由灰色遍历,将已扫描了子对象的对象置为黑色
  • 遍历了所有可达的对象后,所有可达的对象都变成了黑色。不可达的对象即为白色,需要被清理

上面的过程看起来没有什么问题,但是如果在标记过程中,应用程序也在运行,那么对象的指针就有可能改变。这样的话,我们就会遇到一个问题:对象丢失问题

  • 当垃圾收集器扫描到下面情况时
  • 这时候应用程序执行了如下操作
A.c = C;
B.c = null;
  • 此时对象的状态图变成如下情形
  • 这时候垃圾收集器再标记扫描的时候就会导致下面这种情况,此时C是白色,而A已经是黑色的状态了,C被漏标了,被垃圾收集器认为是垃圾需要被清理掉,显然这是不合理的

- SATB

  • 在开始标记的时候生成一个快照图,标记存活对象
  • 在并发标记的时候所有被改变的对象入队(在write barrier里把所有旧的引用所指向的对象都变成非白的)——解决漏标问题
  • 可能存在浮动垃圾,将在下次被收集

- G1混合式回收

  • G1到现在可以知道哪些老的分区可回收垃圾最多。 当全局并发标记完成后,在某个时刻,就开始了Mixed GC。这些垃圾回收被称作“混合式”是因为他们不仅仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的分区
  • Mixed GC也是采用的复制清理策略,当GC完成后,会重新释放空间

- G1分代算法

  • 为老年代设置分区的目的是老年代里有的分区垃圾多,有的分区垃圾少,这样在回收的时候可以专注于收集垃圾多的分区,这也是G1名称的由来。不过这个算法并不适合新生代垃圾收集,因为新生代的垃圾收集算法是复制算法,但是新生代也使用了分区机制主要是因为便于代大小的调整

- SATB详解

  • SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对像就认为是活的,从而开成一个对象图
  • 在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象

如何找到在GC过程中分配的对象呢?

  • 每个region记录着两个top-at-mark-start(TAMS)指针,分别为prevTAMSnextTAMS。在TAMS以上的对象就是新分配的,因而被视为隐式marked。通过这种方式我们就找到了在GC过程中新分配的对象,并把这些对象认为是活的对象

解决了对象在GC过程中分配的问题,那么在GC过程中引用发生变化的问题怎么解决呢?

  • G1给出的解决办法是通过Write BarrierWrite Barrier就是对引用字段进行赋值做了额外处理。通过Write Barrier就可以了解到哪些引用对象发生了什么样的变化
  • mark的过程就是遍历heap标记live object的过程,采用的是三色标记算法,这三种颜色为white(表示还未访问到)、gray(访问到但是它用到的引用还没有完全扫描)、black(访问到而且其用到的引用已经完全扫描完)
  • 整个三色标记算法就是从GC roots出发遍历heap,针对可达对象先标记white为gray,然后再标记gray为black;遍历完成之后所有可达对象都是balck的,所有white都是可以回收的
  • SATB仅仅对于在marking开始阶段进行“snapshot”(marked all reachable at mark start),但是concurrent的时候并发修改可能造成对象漏标记

三色标记算法可能造成漏标的几种情形:

  • 对black新引用了一个white对象,然后又从gray对象中删除了对该white对象的引用,这样会造成了该white对象漏标记
  • 对black新引用了一个white对象,然后从gray对象删了一个引用该white对象的white对象,这样也会造成了该white对象漏标记
  • 对black新引用了一个刚new出来的white对象,没有其他gray对象引用该white对象,这样也会造成了该white对象漏标记

对于三色算法在concurrent的时候可能产生的漏标记问题,SATB在marking阶段中,对于从gray对象移除的目标引用对象标记为gray,对于black引用的新产生的对象标记为black;由于是在开始的时候进行snapshot,因而可能存在Floating Garbage

漏标与误标:误标没什么关系,顶多造成浮动垃圾,在下次GC还是可以回收的,但是漏标的后果是致命的,把本应该存活的对象给回收了,从而影响的程序的正确性

  • 漏标的情况只会发生在白色对象中,且满足以下任意一个条件
    1、并发标记时,应用线程给一个黑色对象的引用类型字段赋值了该白色对象
    2、并发标记时,应用线程删除所有灰色对象到该白色对象的引用,但此时可能有黑色对象引用该白色对象
  • 对于第一种情况,利用post-write barrier,记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一遍
  • 对于第二种情况,利用pre-write barrier,将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根重新扫描一遍

- 停顿预测模型

  • G1收集器突出表现出来的一点是通过一个停顿预测模型根据用户配置的停顿时间来选择CSet的大小,从而达到用户期待的应用程序暂停时间
  • 通过-XX:MaxGCPauseMillis参数来设置。这一点有点类似于ParallelScavenge收集器。 关于停顿时间的设置并不是越短越好
  • 设置的时间越短意味着每次收集的CSet越小,导致垃圾逐步积累变多,最终不得不退化成Serial GC(出现STW,进行Full GC);停顿时间设置的过长,那么会导致每次都会产生长时间的停顿,影响了程序对外的响应时间

- G1收集模式总结

  • Young GC:收集新生代里的region
  • Mixed GC:新生代的所有region + 全局并发标记阶段选出的收益高的老年代region
  • 无论是Young GC还是Mixed GC都只是并发拷贝的阶段

分代GC模式下选择CSet有两种子模式,分别对应Young GC和Mixed GC

  • Young GC:CSet就是所有年轻代里面的region
  • Mixed GC:CSet是所有新生代的region加上在全局并发标记阶段标记出来的收益高的region

- G1的运行过程

  • 会在Young GC和Mixed GC之间不断地切换运行,同时定期地做全局并发标记,在实在赶不上对象创建速度的情况下 使用Full GC(Serial GC)
  • 初始标记是在Young GC.上执行的,在进行全局并发标记的时候不会做Mixed GC,在做Mixed GC的时候也不会启动初始标记阶段
  • 当Mixed GC赶不上对象产生的速度的时候就退化成Full GC,这一点是需要重点调优的地方

G1最佳实践

- 不断调优暂停时间指标

  • 通过XX:MaxGCPauseMillis=x可以设置启动应用程序暂停的时间,G1在运行的时候会根据这个参数选择CSet来满足响应时间的设置。一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度,最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态

- 不要设置新生代和老年代的大小

  • G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标
  • -设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小即可。

- 关注Evacuation Failure

  • Evacuation Failure类似于CMS里面的晋升失败,堆空间的垃圾太多导致无法完成Region之间的拷贝,于是不得不退化成Full GC来做一次全局范围内的垃圾收集

G1回收器日志内容分析

- 测试用例

VM Option:-verbose:gc -Xms10m -Xmx10m -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=200m
public class MyTest1 {
    public static void main(String[] args) {
        int size = 1024 * 1024;

        byte[] myAlloc1 = new byte[size];
        byte[] myAlloc2 = new byte[size];
        byte[] myAlloc3 = new byte[size];
        byte[] myAlloc4 = new byte[size];

        System.out.println("hello world");
    }
}

输出结果:

2020-03-26T10:10:49.680+0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0016222 secs]
   [Parallel Time: 1.1 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 120.5, Avg: 120.5, Max: 120.7, Diff: 0.2]
      [Ext Root Scanning (ms): Min: 0.4, Avg: 0.6, Max: 0.7, Diff: 0.2, Sum: 4.5]
      [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]
      [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]
      [Object Copy (ms): Min: 0.4, Avg: 0.4, Max: 0.4, Diff: 0.1, Sum: 3.0]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
         [Termination Attempts: Min: 3, Avg: 7.8, Max: 14, Diff: 11, Sum: 62]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
      [GC Worker Total (ms): Min: 0.8, Avg: 1.0, Max: 1.1, Diff: 0.2, Sum: 7.9]
      [GC Worker End (ms): Min: 121.5, Avg: 121.5, Max: 121.5, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.4 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 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(4096.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 3946.2K(10.0M)->2816.1K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-03-26T10:10:49.682+0800: [GC concurrent-root-region-scan-start]
2020-03-26T10:10:49.682+0800: [GC pause (G1 Humongous Allocation) (young)2020-03-26T10:10:49.683+0800: [GC concurrent-root-region-scan-end, 0.0008804 secs]
2020-03-26T10:10:49.683+0800: [GC concurrent-mark-start]
, 0.0016254 secs]
   [Root Region Scan Waiting: 0.3 ms]
   [Parallel Time: 0.7 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 123.1, Avg: 123.3, Max: 123.7, Diff: 0.6]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.3, Diff: 0.3, Sum: 0.9]
      [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]
      [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]
      [Object Copy (ms): Min: 0.0, Avg: 0.3, Max: 0.5, Diff: 0.5, Sum: 2.5]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
         [Termination Attempts: Min: 1, Avg: 3.4, Max: 7, Diff: 6, Sum: 27]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 0.1, Avg: 0.5, Max: 0.7, Diff: 0.6, Sum: 3.7]
      [GC Worker End (ms): Min: 123.8, Avg: 123.8, Max: 123.8, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.4 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.2 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 1024.0K(2048.0K)->0.0B(1024.0K) Survivors: 1024.0K->1024.0K Heap: 3881.1K(10.0M)->3992.5K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-03-26T10:10:49.684+0800: [GC concurrent-mark-end, 0.0014553 secs]
2020-03-26T10:10:49.684+0800: [Full GC (Allocation Failure)  3992K->3722K(10M), 0.0028500 secs]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 1024.0K->0.0B Heap: 3992.5K(10.0M)->3722.0K(10.0M)], [Metaspace: 3355K->3355K(1056768K)]
 [Times: user=0.11 sys=0.00, real=0.00 secs] 
2020-03-26T10:10:49.687+0800: [GC remark, 0.0000163 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
2020-03-26T10:10:49.688+0800: [GC concurrent-mark-abort]
hello world
Heap
 garbage-first heap   total 10240K, used 4746K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000)
  region size 1024K, 1 young (1024K), 0 survivors (0K)
 Metaspace       used 3448K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K

创建的字节数组为大对象:2020-03-26T09:44:27.320+0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0048562 secs]

对应Young GC的五个步骤:

  • 根扫描:[Ext Root Scanning (ms): Min: 0.7, Avg: 2.1, Max: 3.2, Diff: 2.5, Sum: 17.1]
  • 更新RS:[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
  • 处理RS:[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
  • 对象拷贝:[Object Copy (ms): Min: 0.0, Avg: 1.0, Max: 2.3, Diff: 2.3, Sum: 8.0]
  • 处理引用队列:[Termination (ms): Min: 0.0, Avg: 0.4, Max: 0.7, Diff: 0.7, Sum: 3.1]

处理card table:[Clear CT: 0.1 ms]

回收集合:[Clear CT: 0.1 ms]

执行完Young GC后堆的使用情况:[Eden: 2048.0K(4096.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 3946.2K(10.0M)->2784.1K(10.0M)]

默认的region大小以及young region个数以及survivor region个数:region size 1024K, 1 young (1024K), 0 survivors (0K)——正好验证了为什么创建的数组都是Humongous,因为数组是连续占用内存空间,大小肯定已经超过一个region大小的50%


转载:https://blog.csdn.net/qq_41982594/article/details/105077188
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场