G1垃圾收集器

        C++和Java之间有一堵由内存动态分配和垃圾收集技术所围成的墙,墙外面的人想进去墙里面的人想出来。
        G1将优化成本均摊到了整个应用空间,它的关注是全局和垃圾收集的收益成本,而不是专注于传统的一次彻底GC,所以它能保证整个应用期间的GC停顿时间在我们设定的期望值时间内。

1、G1(Garbage First)收集器

     G1是一个分代分步的并行收集器,与其他收集器类似,G1垃圾收集器是在jdk1.7中正式提出使用,jdk1.9变为默认的垃圾收集器取代了CMS。G1将堆内存分为新生代和老年代,内存回收主要发生在新生代,而在老年代中也会间歇性回收内存,并且也会有stop-the-world问题。
      为了提高吞吐量G1将一些指定操作只在stw阶段进行,例如全局标记这样耗时间的操作会和用户的应用一起并发进行。为了降低垃圾回收时的stw暂停时长,G1通过并行分步的方式进行垃圾回收。 G1通过对应用和垃圾回收的成本及效率收益进行分析,从而达到可预测的回收停顿时间,通过这些数据的分析和预测来完成暂停时间内需要完的垃圾回收工作。
   G1之所以称之为Garbage Firsst是因为G1首先在回收效率最高的区域(垃圾最多的区域)进行回收,分析得到不同区域的垃圾分布后,通过分散法来回收空间:将存活对象复制分散到新的内存区域,并且在此过程中进行对象整理,这样被占据的那一片内存区域都会释放出来。
      G1收集器 并非实时垃圾回收器,有些不能完成的垃圾回收将会在下次的垃圾回收中完成 他的目标是在长期运行的应用中达到设置的目标暂停时间。 是一个整体长期的优化方案,这也是区别之前的垃圾收集器的主要特点,放眼大局,不是局部,将优化分布到这个那个应用运行期间。
G1的核心优势: Can set Pause Time not Strict.

1.1、G1的使用场景

G1 aims to provide the best balance between latency and throughput using current target applications and environments whose features include:
* Heap sizes up to ten of GBs or larger, with more than 50% of the Java heap occupied with live data.
* Rates of object allocation and promotion that can vary significantly over time.
* A significant amount of fragmentation in the heap.
* Predictable pause-time target goals that aren’t longer than a few hundred milliseconds, avoiding long garbage collection pauses.
The G1 collector achieves high performance and tries to meet pause-time goals

 判断是否需要使用G1,它的目标是为拥有以下特征的应用在运行时达到最短的延迟更高吞吐量平衡点 :

  • (1). 超过10GB的大堆内存,并且堆中有一半以上的存活数据,对大堆内存空间的性能出色;
    • 大堆和多核心cpu;
    • 因为是分区region选择扫描,可以避免扫描大堆空间,减少停顿时间;
  • (2). 对象的分配和晋升速率随着时间变化非常快;
    • 还是避免扫描大堆空间;region扫描,而不是整个堆空间,性能高,时间可控;
  • (3). 堆内存在大量的内存碎片的情况
  • (4). 需要可预测的gc停顿时间,希望gc时长不会超过几百毫秒,避免长时间的gc停顿;
  • 要求可控的停顿时间和吞吐量,一般设置gc时间在300-500ms范围;

1.2、打开G1收集- Enabling G1

    G1垃圾收集器的参数设置比较简单,但是使用越简单,后台工程师做的就越多。
    开发人员仅仅需要声明以下参数即可:
-XX:+UseG1GC -Xmx4g -XX:MaxGCPauseMillis=200

* -XX:+UseG1GC                          //为开启G1垃圾收集器,
* -Xmx4g                                      //设计堆内存的最大内存为4G,
* -XX:MaxGCPauseMillis=200      //设置GC的最大暂停时间为200ms。
 

1.3、堆内存布局-Heap layout

     官方提倡G1取代CMS收集器,同时也是JDK9的默认收集器。下面介绍一下G1是如何做到在控制gc时间的同时又保持着如此高的吞吐量的:
     上图中可以看出各个 区域逻辑上并不是连续的,年轻代包括 eden区(红色部分)和 survior区域(红色标记为s部分)。这两个区域与其他收集器中的eden和survior提供了同样的功能,但是有所区分的是G1中的内存区域不是连续的。 淡蓝色部分的块组成了老年代,老年代的划分可能会相对大一些 大内存区(比如带有H字样的块),这样设置的目的是因为有一些大的对象会横跨多个区域。正常情况下应用总是会分配到新生代,也就是eden区,但是例外的是大对象会被直接分配到老年代。
    G1会对整个新生代进行垃圾回收,并且同时对部分需要回收的老年代也进行回收。在回收阶段G1将一个Region中的对象拷贝到一个或者多个不同的Region中,目标区块由被回收对象所在的区块决定,整个新生代存活的对象将被拷贝到survior或者老年代,老年代的对象拷贝通过年代的年龄再具体区分,避免了类似CMS空间碎片的产生。

1.4、区域(Region)

区域Region的意义    
    G1里面的Region区域的概念是不同于其他垃圾收集的一个重要区别,也是思维的方式不同,后续垃圾收集的单位都是以Region为单位的,但仍然属于分代收集器。正是由于内存被划分为小块,避免扫描整个空间堆, 要想更好的控制GC停顿时间就得有灵活扫描堆内存和回收堆内存的选择权利,而Region让这一切成为了可能 Region的出现不必让线程每次都必须全部扫描堆文件并且一次回收全部的垃圾,让GC时间可控,并且还保留了之前垃圾收集的分代思想,可以说G1进行了取长补短,在保留之前的垃圾收集的优势后又做了一个优化升级。
Region区的特点 对堆进行了 重新布局逻辑上存在young,old,eden,但 物理上已经不是隔离的了;
  • 通过region实现垃圾收集不必全堆扫描,可选取部分region区进行回收,进行收益的排序;
  • 通过region实现可筛选价值的评估回收,达到用户设定的期望;
  • 默认分为: 2048个分区
  • 没个区块大小范围为:1~32M
    Region是实现G1算法的基础,每个Region的大小相等,通过-XX:G1HeapRegionSize参数可以设置Region的大小, 大小区间只能是:1M,2M,4M,8M,16M,这样我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够, 默认值如下:
 

2、G1垃圾收集二阶段

The following list describes the phases, their pauses and the transition between the phases of the G1 garbage collection cycle in detail:
1. Young-only phase: This phase starts with a few Normal young collections that promote objects into the old generation. The transition between the young-only phase and the space-reclamation phase starts when the old generation occupancy reaches a certain threshold, the Initiating Heap Occupancy threshold. At this time, G1 schedules a Concurrent Start young collection instead of a Normal young collection.
    * Concurrent Start : This type of collection starts the marking process in addition to performing a Normal young collection. Concurrent marking determines all currently reachable (live) objects in the old generation regions to be kept for the following space-reclamation phase. While collection marking hasn’t completely finished, Normal young collections may occur. Marking finishes with two special stop-the-world pauses: Remark and Cleanup.
    * Remark: This pause finalizes the marking itself, performs global reference processing and class unloading, reclaims completely empty regions and cleans up internal data structures. Between Remark and Cleanup G1 calculates information to later be able to reclaim free space in selected old generation regions concurrently, which will be finalized in the Cleanup pause.
    * Cleanup: This pause determines whether a space-reclamation phase will actually follow. If a space-reclamation phase follows, the young-only phase completes with a single Prepare Mixed young collection.

2. Space-reclamation phase: This phase consists of multiple Mixed collections that in addition to young generation regions, also evacuate live objects of sets of old generation regions. The space-reclamation phase ends when G1 determines that evacuating more old generation regions wouldn't yield enough free space worth the effort.
循环垃圾回收,大致来说,G1回收期存在两个周期:
年轻阶段(The young-only phase ):是指在老年代区域没达到指定阈值效益的情况下的独立垃圾回收,如果老年代不满足回收期望值,这时候会放弃对老年代的垃圾回收;
空间回收阶段(The space-reclamation phase) :是G1在从新生代回收内存以外,老年代达到回收阈值,再从老年代逐步回收内存的阶段,两个阶段循环执行;
 

2.1、年轻代垃圾回收阶段

    该阶段随着一些年轻代的对象逐步晋升到老年代而开始,当老年代的内存占用率达到一定阈值这两个阶段会发生转换。此时,G1会初始化一次对年轻代和老年代的并发标记来代替年轻代阶段的回收,称为Concurrent start。触发的默认条件为:当堆的使用率达到45%(-XX: InitiatingHeapOccupancyPercent 默认值时45,触发并发标记周期的执行);
    Initial Mark : 在年轻代回收以外要开始一次标记过程。并通过并发标记来决定所有要被保留的老年代存活对象(为下一个回收阶段准备)。在标记未完成的时候,年轻代会依然继续执行垃圾回收。在两次特殊的stw停顿后,标记完成, 这两次停顿分别是: Remark和Cleanup.
    Remark: Remark会完成所有的标记,并且进行全局关联和类卸载。在Remark和cleanup之间G1会并发得计算总结出所有的存活信息,这些信息在cleanup暂停中会用来更新内部数据。
    Cleanup: 该阶段会回收所有的空白区域,并且决定是否接下来要进行空间回收动作。如果接下来需要进行空间回收,将通过一次年轻代的回收动作来完成。

2.2、堆空间回收阶段

    除了单纯的年轻代区域回收之外,此阶段由若干个次混合的年轻代和老年代垃圾组成,同时包含年轻代和老年代里的存活对象。随着分散回收的进行,当G1觉得分散更多的老年代不会产生与之相匹配的空白空间时,回收阶段便告一段落。
    在空间回收阶段后,回收循环重启,再次来到年轻阶段,另外作为后补垃圾收集机制,当应用内存不足以用来收集所需存活对象信息的时候,G1会启动一次full gc。

2.3、GC模式

常说的 G1有两种垃圾回收模式:
  • YoungGC 收集年轻代里的Region;
  • MixGC :当堆使用达到阈值后,堆young区 + 部分old区域的Region进行垃圾回收;不是物理上的young和old,而是逻辑上的区域;
Young gc
发生在年轻代的GC算法,一般对象(除了巨型对象)都是在eden region中分配内存,当所有Eden region被耗尽无法申请内存时,就会触发一次Young gc,这种触发机制和之前的Young gc差不多,执行完一次Young gc,活跃对象会被拷贝到survivor region或者晋升到Old region中,空闲的region会被放入空闲列表中,等待下次被使用。
设置参数如下:
  • -XX:MaxGCPauseMillis | 设置G1收集过程目标时间,默认值200ms;
  • -XX:G1NewSizePercent | 新生代最小值,默认值5%;
  • -XX:G1MaxNewSizePercent | 新生代最大值,默认值60%;
Mixed GC
 如名字一样,“混合收集” 不仅进行正常的新生代垃圾收集,同时也回收线程标记的老年代分区。当越来越多的对象晋升到老年代Old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old gc,除了回收整个Young region,还会回收一部分的Old region,
这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。
那么Mixed GC什么时候被触发?先回顾一下CMS的触发机制,如果添加了以下参数:
  • -XX:CMSInitiatingOccupancyFraction=80
当老年代的使用率达到80%时,就会触发一次cms gc。相对的,Mixed GC中也有一个阈值参数:
  •  -XX:InitiatingHeapOccupancyPercent=45,
当老年代大小占整个堆大小百分比达到该阈值为45%时,会触发一次Mixed GC.

3、G1的内部原理

3.1、决定初始的堆占用值

      初始的堆占用值(IHOP) 是用来启动标记回收的阈值,该值表示老年代空间被占用的百分比。
      默认情况下G1会在标记循环中通过标记使用的时间和老年代内存的分配值来自动算出最合适的初始占用值。该特性被称作Adaptive IHOP。如果启用了该特性,我们就可以用-XX:InitiatingHeapOccupancyPercent来决定IHOP的初始值,因为一开始G1是没办法通过之前的循环算出该值的。我们也可以使用-xx:-G1UseAdaptiveIHOP 选项来禁用该特性,这样我们设置的默认初始值就会一直生效。

3.2、标记

      G1的标记过程使用了一个叫做Snapshot-At-The-Beginning (SATB)的快照算法,在启动标记暂停的时候,他会为当前堆做一个虚拟快照,在标记开始时存活的对象会被认为在接下来的标记过程中一直存活,这意味着即使该对象在标记过程中已经死亡我们还是认为他是存活的, 与其他收集器相比,这一策略可能会导致我们保留了一些已经失效的对象,但是没有关系,尽管这样SATB在Remark阶段依然提供了更好的延迟,况且这些被误保留的对象在下一次标记中一样会被清除, 也正是因为此策略G1保证了应用期间的时间控制,它将优化成本均摊到了整个应用期间,不会出现类似CMS一次gc耗时200ms,另一次出现1000ms的情况,G1会根据设定值稳定在300ms左右。

3.3、大对象Humongous区域

      在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了 分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储,但是为了能找到连续的H区,有时候不得不启动Full GC。当前的块区块大小是默认设置的,除非我们显示指定,可以通过-xx:G1HeapRegionSize选项进行。
大对象有的时候会被进行特殊对待:
  1. 每个大对象都被连续分配在老年代。对象的起始总是被分配在序列中第一个区块的起始,区块中剩下的空间将不再使用,除非该对象被回收。
  2. 通常来说, 大对象只有在标记清理的最后阶段或者Full gc中才会被清除。但是有个例外便是,对于基本类型的数组对象G1有机会在任意回收阶段对该数组进行回收。此特性默认打开,可以通过XX:G1EagerReclaimHumongousObjects进行关闭。
  3. 大对象的分配可能会导致gc过早发生,每次一有大对象分配,G1就会检查IHOP从而可能导致标记过程的发生。
  4. 大对象在gc过程中是不会移动的(因为这个复制消耗是不可接受的,这也是老年代为什么不采用复制算法的原因),即便是Full gc,这也可能导致Full gc非常缓慢甚至OOM问题,因为大对象的存在会导致大量的内存碎片。

3.4、年轻代回收的空间标准

      在年轻回收阶段, 需要进行回收的区块,仅仅由年轻代组成。在此阶段,G1通过长期观察实际的暂停时间,总是不断的自动调整年轻代空间的大小来最终达到我们设定的-XX:MaxGCPauseTimeMillis 和-XX:PauseTimeIntervalMillis暂停时间目标。期间还会检查多少对象需要拷贝,以及这些对象之间是如何关联的。
      如果这样行不通,那么G1会在设置的-XX:G1NewSizePercent 和 -XX:G1MaxNewSizePercent之间自适应调整年轻代大小从而来争取达到目标暂停时间。
Young Generation Size: Avoid explicitly setting young generation size with the -Xmn option 
or any or other related option such as -XX:NewRatio. 
Fixing the size of the young generation overrides the target pause-time goal.
  • 所以官方在gc调优的时候就建议 不要手动设置新生代和老年代的大小,只要设置整个堆的大小即可:
  • G1收集器在运行过程中,会自己调整新生代和老年代的大小 其实是通过自动调整young代的大小来调整对象晋升的速度,从而达到为收集器设置的暂停时间目标,如果手动设置了大小就意味着放弃了G1的自动调优, 破坏了停顿时间策略;

3.5、堆空间回收的标准

      在堆空间回收阶段,包含年轻代和老年代,G1会尝试通过一次暂停来尽可能多得回收老年代空间。年轻代的大小会被设置为最小允许值,并且会添加所有需要进行收集的老年代区块直到G1认为无法在指定时间内收集完这些区块。在暂停期间,G1通过区块的回收效率和剩余的可用回收时间来决定最终要回收的区块集。每次选中的回收块都是从需要回收的候选区块中选出的,候选者的标准是存活比例低于-XX:G1MixedGCLiveThresholdPercent的区块。
当候选区域中未回收的比例低于-XX:G1HeapWastePercent设置的值的时候,此阶段便告一段落。

4、G1 默认参数

-XX:MaxGCPauseMillis=200 最大可暂停时间
-XX:GCPauseTimeInterval=<ergo> 暂停间隔时间,默认由G1自己控制,允许背靠背执行
-XX:ParallelGCThreads=<ergo> STW线程数,低于8核心建议设置为核心数,大于的可以设置为核心数量的8分之5
-XX:ConcGCThreads=<ergo> 设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。
-XX:+G1UseAdaptiveIHOP -XX:InitiatingHeapOccupancyPercent=45 设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。
-XX:G1HeapRegionSize=<ergo> 设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域。
-XX:G1NewSizePercent=5 -XX:G1MaxNewSizePercent=60 设置要用作年轻代大小最大值的堆大小百分比。
-XX:G1HeapWastePercent=5 设置您愿意浪费的堆百分比。当比例低于此值,G1就会停止回收
-XX:G1MixedGCCountTarget=8 设置标记周期完成后,对存活数据上限为 G1MixedGCLIveThresholdPercent 的旧区域执行混合垃圾回收的目标次数。默认值是 8 次混合垃圾回收。混合回收的目标是要控制在此目标次数以内
-XX:G1MixedGCLiveThresholdPercent=85 老年代中存活对象比例超过此值的区块不会被放入回收阶段。
G1有一些特性来提高效率:
  • G1可以在任意回收阶段回收老年代的完整的空白区块。这可以避免很多不必要的回收动作,并且以很低的代价释放出大量空间。
  • G1可以选择性地尝试对堆上重复的Java字符串进行去重,减少内存的占用。 可以通过-XX:+G1EnableStringDeduplication来打开此特性。

5、CMS与G1的区别

(1)分代收集
  • CMS收集器是老年代的收集器,gc的时候只回收Old区,可以配合新生代的Serial和ParNew收集器一起使用;
  • G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用,对G1来说是区分young gc和Mixed gc,前者对应年轻代的垃圾回收,后者混合了年轻代和部分老年代的收集,因此每次收集肯定会回收年轻代,老年代根据内存情况可以不回收或者回收部分或者全部。G1的分代更多是逻辑上的概念,G1将内存分成多个等大小的region,Eden/ Survivor/Old分别是一部分region的逻辑集合,物理上内存地址并不连续。
(2)空间碎片
  • CMS收集器是使用“标记-清除”算法进行的垃圾回收,有空间碎片;
  • G1收集器使用的是“标记-整理”算法,进行了空间整合,减少空间碎片;
(3)内存布局
  • G1: region:内存的划分不一样,g1使用的是region区域来划分的,逻辑上分成young和old,不一定连续;
  • CMS:【Eden,form,to,old,premer】连续的内存区域;
(4)设计目标
  • CMS收集器以最小的停顿时间为目标的收集器,提高系统响应能力;
  • G1收集器以可预测可选择-可控的垃圾回收停顿时间为目标,尽量避免出现长时间GC停顿;
举例:垃圾清理过程需要耗时:5ms标记,垃圾清理需要:10ms,假设我们都设置了或者说期望的停顿时间为10ms:
结果:cms耗时:15ms,g1耗时:10ms以内;
区别:因为G1通过Region对回收结果可控,可选择,更加智能;但是CMS只是采用了并行的方式一次清理完所有的垃圾,不管多久;
注意:g1的时间不能太严苛,如果时间太严格,由于清理不彻底,每次预留有垃圾,阶梯上涨这样容易产生OOM;

5.1、选择G1代替CMS的优点

  • 1、简单:开发者控制调优变的简单;
  • 2、并行与并发:充分利用cpu,减少STW的时间;
  • 3、可预见性与可控性:可预测的停顿模型,G1可选取部分区域进行回收,可以缩小回收范围,控制减少全局停顿;
  • 4、空间整理:回收时分区Region进行适当移动整理,避免了CMS的垃圾碎片;
  • 5、大空间分配:堆内存的Region布局;使其对超大堆的表现更出色;

6、问题与思考

思考一:如果仅仅是GC回收新生代对象,如何解决不同Region区域的引用,如何找到所有的根对象呢?
      在垃圾回收的时候都是从Root开始搜索,这会先经过年轻代再到老年代,对于年轻代引用老年代的这种跨代不需要单独处理。但是老年代引用年轻代的会影响young gc,这种跨代需要处理。 这里CMS和G1都用到了 Card Table ,一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡,卡通常较小,一个字节对应一个Card。当一个Card上的对象的引用发生变化的时候,就将这个Card对应的Card Table上的状态置为dirty,young gc的时候扫描状态是dirty的Card即可,CMS的老年代就会记录这样一个Card tbale。 对于G1垃圾收集,又引入了Rset(Remembered Set),在老年代中有一块区域用来记录指向新生代的引用。 无论G1还是其他分代收集器,JVM都是使用Card table来避免全局扫描,思想有点儿类似于数据库的全局事务快照,只是记录某一时刻的全局事务id即可,不必全局扫描表记录和备份数据。
      我们知道G1垃圾收集器将内存分为了不同的Region区域,不再以严格的年轻代和老年代来区分内存并进行垃圾回收,如果需要扫描整个old区,势必会浪费很多的时间,且扫描了一些不必要的Region区域。 G1通过RSet,每个Region中都有一个 RSet,记录的是其他Region中的对象引用本Region对象的关系, 是一种point-in的关系,即:谁引用了我的对象因为G1分了很多Region,需要回收那个区域的时候,只需要判断要回收的区域是否有其他对象引用了该区域里的对象,即只需要找待回收区域的根对象即可,避免无效扫描。若存储point-out关系,将会扫描很多无关的Region区,造成时间性能的浪费。 这里面还有另外一个集合:Collection Set,简称:CSet,CSet记录的是GC要收集的Region的集合,CSet里的Region可以是任意代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。如果Rset集合的引用对象较多,这里为了提高引用对象的查找和赋值处理问题,又通过卡表(Card Table)来实现查询和赋值,一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。RSet其实是一个Hash Table,Key是别的调用方Region的起始地址,Value是一个集合,里面的元素是Card Table的Index分区的地址。如下图所示:
如上图所示,要回收年轻代的region A,只需要扫描C,D,F 区域的根对象即可,而不需要扫描整个old区。
分代G1模式下选择CSet有两种子模式,分别对应YoungGC和mixedGC:
  • * YoungGC:CSet就是所有年轻代里面的Region;
  • * MixedGC:CSet是所有年轻代里的Region加上在全局并发标记阶段标记出来的收益高的Region;
思考二 : 如何解决对象在GC过程中分配的问题呢?   
      初始快照算法:snapshot-at-the-beginning (SATB),SATB是维持并发GC的一种手段,类似数据库事务一致性读视图快照。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图。 在GC收集的时候,新产的对象认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象。
   如何找到在GC的过程中分配的对象呢?每个region记录着两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象就是新分配的,因而被视为隐式marked。通过这种方式我们就找到了在GC过程中新分配的对象, 并把这些对象认为是活的对象
思考三: 在GC过程中引用发生变化的问题怎么解决呢?       
三色标记算法:在并发标记中,通过三色标记法来完成对对象是否存活以及追踪的记录。比如我们定义三种颜色并赋予以下的意义:
  • 黑色:根对象,或者该对象与它的子对象都被扫描;
  • 灰色:对象本身被扫描,但还没扫描完该对象中的子对象;
  • 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象;
如果在GC运行中,对象的引用关系发生来如下的变化:
如上图所示,gc运行过程中,C对象的引用关系发生来改变,D引用C显然按照三色标记法C为白色是要被清理的,显然不太合理。所以这里需要记录此种改变。
思考四: 可预测的停顿如何实现?
      G1记录跟踪了各个Region获取垃圾收集的价值大小,在后台维护一个优先列表;每次根据用户设置的允许的收集时间,优先回收价值最大的Region,可以有计划的避免全区的垃圾收集,这也是Garbage-First的由来,也是与CMS最大的区别;这就保证了在有限的时间内可以获取尽可能高的收集效率;
思考五: 什么情况下会发生fullgc?
导致CMS FullGC的原因有两个:
  1.  Promotion Failure:在年轻代晋升的时候老年代没有足够的连续空间容纳,很有可能是内存碎片导致的;
  2.  Concurrent Mode Failure:由于对象晋升过快或者有大对象的分配,在并发过程中jvm觉得在并发过程结束前堆就会满了,需要提前触发Full GC。
导致G1 Full GC的原因可能有两个,与CMS类似:
  1. 年轻代回收空间的时候没有足够的to-space来存放晋升的对象,直接晋升给老年代,并发处理过程完成之前老年代的空间也耗尽;
  2. 设置的时间太过严格,导致垃圾收集的速度低于垃圾生产的速度,空间耗尽;
G1的初衷就是要避免Full GC的出现,Full GC会会对所有region做Evacuation-Compact,而且是单线程的STW,非常耗时间。
其他:
  1. 方法区空间不够用了;
  2. 手动调用System.gc(),一般不建议手动调用;
Full-GC的影响?
  1. 服务体验:服务停止,无法响应用户请求,严重影响用户的体验;
  2. 数据一致性:分布式服务中容易引起主备切换,出现脑裂;

7、小结

     G1垃圾收集是一款兼顾新生代和老年代垃圾收集器,是JDK7提供的一个新的面向服务端应用的垃圾收集器,JDK9的默认垃圾收集,逐渐取代了CMS垃圾回收。其实也不一定说G1就一定好,垃圾收集器参数的设定及垃圾回收器的选择一定要根据具体的服务及场景来判断选择,没有完美的唯一解决方案。
    我们可以把垃圾收集器进行一个分类垃圾收集器分类:
        1、串行收集器 ->Serial和Serial Old 只能有一个垃圾回收线程执行,用户线程暂停。 
       适用场景:于内存比较小的嵌入式设备;
         2、并行收集器[吞吐量优先]- >Parallel Scanvenge、Parallel Old 多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
                使用场景:适用于CPU密集型,科学计算,后台处理等弱交互场景;
         3、并发收集器[停顿时间优先] ->CMS、G1,用户线程和垃圾收集线程同时或交替执行,垃圾收集线程在执行的时候不会停顿用户线程的运行。 
                使用场景:适用于用户交互密集型,对时间有要求的场景,比如web应用;
 
 
  OK ---会当凌绝顶,一览众山小。  
 
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。
 
参考资料:
《深入了解jvm虚拟机》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值