Java8的G1垃圾回收器官方文档参考:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection
一、Garbage-First垃圾收集器 Garbage-First Garbage Collector
Garbage-First (G1) 垃圾收集器是一个服务器风格的垃圾收集器,目标是具有大内存的多处理器计算机。它尝试以高概率满足垃圾收集(GC)停顿时间目标,同时实现高吞吐量。整个堆操作,如全局标记,是与应用程序线程并发执行的。这可以防止与堆或实时数据大小成比例的中断。
G1收集器通过几种技术实现了高性能和停顿时间目标。
堆被划分为一组大小相同的堆区域,每个区域都是连续的虚拟内存范围。G1执行一个并发的全局标记阶段,以确定整个堆中对象的活动。标记阶段完成后,G1知道哪些区域是空的。它首先收集这些区域,这通常会产生大量的自由空间。这就是为什么这种垃圾收集方法被称为Garbage-First。顾名思义,G1将其收集和压缩活动集中在堆中可能充满可回收对象(即垃圾)的区域。G1使用停顿预测模型来满足用户自定义的停顿时间目标,并根据指定的停顿时间目标选择收集的区域数量。
G1将对象从堆的一个或多个区域复制到堆上的单个区域,并在此过程中压缩和释放内存。这种疏散在多处理器上并行执行,以减少停顿时间并增加吞吐量。因此,对于每次垃圾收集,G1都在不断地减少碎片。这超出了前面两种方法的能力。CMS (Concurrent Mark Sweep)垃圾收集不会进行压缩。并行压缩只执行整个堆压缩,这会导致相当长的暂停时间。
需要注意的是,G1不是实时收集器。它满足设定的停顿时间目标的概率大,但不是绝对确定的。根据以前收集的数据,G1估计在目标时间内可以收集多少个区域。因此,收集器有一个相当准确的区域收集成本模型,它使用这个模型来确定在停顿时间目标内收集哪些区域和多少区域。
G1的第一个重点是为运行需要大堆且GC延迟有限的应用程序的用户提供解决方案。这意味着堆大小约为6 GB或更大,稳定且可预测的停顿时间低于0.5秒。
如果应用程序具有以下一个或多个特征,那么使用CMS或并行压缩的应用程序将从切换到G1中受益。
- 超过50%的Java堆被活跃数据占用。
- 对象分配率或晋升率差异较大。
- 应用程序正在经历不希望的长时间垃圾收集或压缩停顿(超过0.5到1秒)。
G1计划作为并发标记扫描收集器(CMS)的长期替代品。将G1与CMS进行比较,可以发现G1是更好的解决方案。一个区别是G1是一个压缩收集器。此外,G1提供了比CMS收集器更可预测的垃圾收集停顿,并允许用户指定所需的停顿目标。
与CMS一样,G1是为需要更短GC暂停的应用程序设计的。
G1将堆划分为固定大小的区域(灰色框),如图9-1“由G1划分堆”所示。
这个图形由一个10乘10的网格组成。网格的大部分单元格是灰色的。19个单元格被涂成深蓝色。这些深蓝色的单元格随机分布在网格的上六行。其中两个深蓝色的单元格里有一个红色的盒子。一个两格宽、一格高的单元格(出现在第一行)和一个三格宽、一格高的单元格(出现在第六行)被涂成深蓝色,并标记为"H"。8个单元格被涂成浅蓝色,并包含一个红色的盒子。其中两个单元格被标记为"S"。这些带红框的浅蓝色单元格随机分布,大部分位于网格的上半部分。
从逻辑上讲,G1是分代的。一组空区域被指定为逻辑年轻代。在图中,年轻代是浅蓝色的。分配是在逻辑年轻代中完成的,当年轻代填满时,该区域集被垃圾收集(年轻代收集)。在某些情况下,年轻区域集(深蓝色的年老区域)之外的区域可以同时被垃圾收集。这被称为混合集合。在图中,收集的区域用红色框标记。该图说明了混合收集,因为同时收集年轻区域和年老区域。垃圾收集是一个压缩收集,它将活动对象复制到选定的、最初为空的区域。根据幸存对象的年龄,该对象可以复制到幸存区域(用“S”标记)或年老区域(没有特别显示)。标有“H”的区域包含大于半个区域的巨大对象,并经过特殊处理;请参阅Humongous对象和Humongous分配在垃圾优先垃圾收集器一节。
二、分配(疏散)失败 Allocation (Evacuation) Failure
与CMS一样,G1收集器在应用程序继续运行的同时运行其部分收集,存在应用程序分配对象的速度快于垃圾收集器恢复空闲空间的风险。请参阅“并发标记清除(CMS)收集器的并发模式失败”一节,了解类似的CMS行为。在G1中,当G1将活动数据从一个区域(疏散)复制到另一个区域时,会发生故障(Java堆耗尽)。复制是为了压缩实时数据。如果在清理正在被垃圾收集的区域期间找不到空闲(空)区域,则会发生分配失败(因为没有空间来分配正在清理的区域中的活动对象),并完成stop-the-world (STW)完全收集。
三、浮动垃圾 Floating Garbage
对象可能在G1收集期间死亡,而不会被收集。G1使用一种称为**开始快照(SATB-Snapshot At The Beginning)**的技术来保证所有活动对象都被垃圾收集器找到。SATB规定,任何在并发标记(对整个堆的标记)开始时处于活动状态的对象都被认为是用于收集目的的活动对象。SATB允许以类似于CMS增量更新的方式浮动垃圾。
四、停顿 Pauses
G1暂停应用程序,将活动对象复制到新区域。这些暂停可以是只收集年轻区域的年轻收集停顿,也可以是清除年轻和年老区域的混合收集停顿。与CMS一样,当应用程序停止时,会有一个最后的标记或标记停顿来完成标记。虽然CMS也有初始标记暂停,但G1将初始标记工作作为疏散暂停的一部分。G1在收集结束时有一个清理阶段,该阶段部分是STW,部分是并发的。清理阶段的STW部分标识空区域,并确定作为下一个收集候选者的年老区域。
五、卡表和并发阶段 Card Tables and Concurrent Phases
记忆集(参考第八部分)是我们针对于跨代引用问题提出的思想,而卡表则是针对于该种思想的具体实现。
如果垃圾收集器没有收集整个堆(增量收集),垃圾收集器需要知道从堆的未收集部分到正在收集的堆部分的指针在哪里。这通常用于分代垃圾收集器,其中堆中未收集的部分通常是年老代,而收集的堆部分是年轻代。保存该信息的数据结构(老一代指向年轻一代对象)是一个记忆集。卡表是一种特殊类型的记忆集合。
卡表卡页:
- Java HotSpot VM使用字节数组作为卡表 CARD_TABLE[]。每个字节称为一张卡页。卡页对应于堆中的地址范围。脏卡意味着将字节的值更改为脏值;脏值可能包含从老一代到年轻一代的新指针,地址范围由卡页覆盖。
- Java HotSpot VM使用的卡页是2^9大小,即512字节.
- 一个卡页中可包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成1,表示该元素变脏,否则为0。GC时,只要筛选本收集区的卡表中变脏的元素加入GC Roots里。
- 处理一张卡页意味着查看这张卡页,看是否有一个老一代到年轻一代的指针,也许会对这些信息做些什么,比如把它传输到另一个数据结构。
卡表其他作用:
-
老年代识别新生代的时候
-
对应的card table被标识为相应的值(card table中是一个byte,有八位,约定好每一位的含义就可区分哪个是引用新生代,哪个是并发标记阶段修改过的)
G1具有并发标记阶段,用于标记从应用程序中找到的活动对象。并行标记从疏散停顿结束(初始标记工作完成的地方)延伸到重新标记。并发清理阶段将集合清空的区域添加到空闲区域列表中,并清除这些区域的记忆集。此外,一个并发细化线程根据需要运行,以处理被应用程序写操作弄脏的卡片表条目,这些条目可能具有跨区域引用。
六、开始并发收集周期 Starting a Concurrent Collection Cycle
如前所述,年轻区域和年老区域都在混合收集中进行垃圾收集。为了收集年老区域,G1对堆中的活动对象进行完整的标记。这样的标记是通过并发标记阶段完成的。当整个Java堆的占用达到参数InitiatingHeapOccupancyPercent的值时,将启动一个并发标记阶段。使用命令行选项设置该参数的值-XX:InitiatingHeapOccupancyPercent=。InitiatingHeapOccupancyPercent的默认值为45。
七、停顿时间目标 Pause Time Goal
使用MaxGCPauseMillis标志为G1设置停顿时间目标。G1使用一个预测模型来决定在目标停顿时间内可以完成多少垃圾收集工作。在一个集合结束时,G1选择要在下一个集合(集合集)中收集的区域。集合集将包含年轻区域(其大小的总和决定了逻辑年轻代的大小)。部分是通过选择集合中年轻区域的数量,G1对GC停顿的长度施加控制。您可以像使用其他垃圾收集器一样在命令行上指定年轻代的大小,但是这样做可能会妨碍G1获得目标停顿时间的能力。除了停顿时间目标之外,您还可以指定停顿发生的时间段长度。您可以指定这个时间跨度(GCPauseIntervalMillis)的最小变量使用量以及停顿时间目标。MaxGCPauseMillis的默认值是200毫秒(Java9以上版本,Java8默认值约49小时)。GCPauseIntervalMillis(0)的默认值相当于对时间跨度没有要求。(Java9以上版本默认201ms,Java8默认值0)
八、记忆集 Rsets
G1 GC通过将活跃对象从一个或多个区域(称为收集集合(CSet))增量并行复制到一个或多个不同的新区域来实现压缩,从而减少堆碎片。目标是回收尽可能多的堆空间,从那些包含最多可回收空间的区域开始,同时尝试不超过停顿时间目标(垃圾优先)。
G1 GC使用独立的记忆集(RSets)来跟踪对区域的引用。独立RSets支持区域的并行和独立集合,因为只有一个区域的RSet必须扫描到该区域的引用,而不是整个堆。G1 GC使用写后屏障(post-write barrier)来记录对堆的更改并更新RSets。
当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况。
RSet空了,说明这个Region中的中的对象已经没有被其他对象引用了。
记录老年代的引用(因为一般未被收集的都是老年代的对象,对于当前region中的对象来说就是记录谁引用了自己):
老年代引用老年代对象,Rset保存在老年代中
老年代引用年轻代对象,Rset保存在年轻代中
九、三色标记
在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。这里引入“三色标记”来给大家解释下,把Gcroots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以下三种颜色:
黑色:
表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。
黑色的对象代表已经扫描过, 它是安全存活的,
如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。
黑色对象不可能直接(不经过灰色对象) 指向某个白色对象。
灰色:
表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。
白色:
表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段,
所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达。
标记过程:
-
初始时,所有对象都在 【白色集合】中;
-
将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
-
从灰色集合中获取对象:
-
将本对象 引用到的 其他对象 全部挪到 【灰色集合】中;
-
将本对象 挪到 【黑色集合】里面。
重复步骤3.4,直至【灰色集合】为空时结束。
结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收
十、漏标和写屏障
漏标只有同时满足以下两个条件时才会发生:
-
条件一:灰色对象 断开了 白色对象的引用;即灰色对象 原来成员变量的引用 发生了变化。
-
条件二:黑色对象 重新引用了 该白色对象;即黑色对象 成员变量增加了 新的引用。
漏标会导致被引用的对象被当成垃圾误删除,需要解决:
- CMS中使用增量更新(Incremental Update)处理,利用写后屏障(post-write barrier)
- G1中使用原始快照SATB(Snapshot At The Beginning),利用写前屏障(pre-write barrier)(更新RSets使用的是写后屏障)
- ZGC中使用读屏障(不同于java内存模型的读屏障,类似与面向切面的处理)
增量更新就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后, 它就变回灰色对象了。
原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)
写前屏障,在等号赋值之前,将Object.filed原有的引用对象记录下来(相当于排个快照,SATB)
Object.filed = New_Object
写后屏障,在等号赋值之后,将新的引用对象New_Object记录下来
十一、G1垃圾收集器调优 Garbage-First Garbage Collector Tuning
本节介绍如何调整和调优 Garbage-First垃圾收集器(G1 GC)以进行评估、分析和性能。
G1 GC是一个区域化的分代垃圾收集器,这意味着Java对象堆(堆)被划分为许多大小相同的区域。在启动时,Java虚拟机(JVM)设置区域大小。区域大小从1 MB到32 MB不等,具体取决于堆大小。目标是不超过2048个区域。伊甸园、幸存者和老年代是这些区域的逻辑集合,并且不是连续的。
G1 GC有一个它试图满足的停顿时间目标(软实时)。 在年轻代收集期间,G1 GC调整其年轻代(伊甸园和幸存者大小)以满足软实时目标。 请参阅“暂停和垃圾优先垃圾收集器中的暂停时间目标”章节(第四和第七部分),了解G1 GC为什么要暂停以及如何设置暂停时间目标。
在混合收集期间,G1 GC根据混合垃圾收集的目标数量、堆中每个区域中的活动对象的百分比和总体可接受的堆垃圾百分比来调整收集的年老区域的数量。
1. 垃圾收集阶段 Garbage Collection Phases
除了组成stop-the-world (STW) 年轻和混合垃圾收集的疏散停顿(参见垃圾优先收集器中的分配(疏散)失败部分)外,G1 GC还具有并行、并发和多阶段标记周期。G1 GC使用开始时快照(SATB)算法,该算法在标记周期开始时从逻辑上获取堆中活动对象集的快照。活动对象集还包括自标记周期开始以来分配的对象。G1 GC标记算法使用一个写前屏障(pre-write barrier)来记录和标记属于逻辑快照一部分的对象。
2. 年轻代的垃圾收集 Young Garbage Collections
G1 GC满足来自添加到伊甸园区域集的区域的大多数分配请求。在年轻代垃圾收集期间,G1 GC从前面的垃圾收集中收集伊甸园区域和幸存者区域。来自伊甸园和幸存者区域的活跃对象被复制或疏散到一组新的区域。特定对象的目标区域取决于对象的年龄;一个已经足够老的对象疏散到一个老年代区域(也就是说,它被提升);否则,该对象将撤离到幸存者区域,并将被包含在下一个年轻代或混合垃圾收集的CSet(Collection Sets)中。
3. 混合垃圾收集 Mixed Garbage Collections
成功完成并发标记周期后,G1 GC将从执行年轻垃圾收集切换到执行混合垃圾收集。在混合垃圾收集中,G1 GC可选地将一些旧区域添加到将要收集的eden和survivor区域集合中。添加的旧区域的确切数量由许多标志控制(请参阅建议一节中的“改良混合垃圾收集器”)。在G1 GC收集足够数量的旧区域(通过多个混合垃圾收集,默认8个)后,G1恢复执行年轻垃圾收集,直到下一个标记周期完成。
4. 标记周期的各个阶段 Phases of the Marking Cycle
标记周期包括以下几个阶段:
- 初始标记阶段:在此阶段,G1 GC标记根。此阶段由正常的(STW)年轻代垃圾收集负责。
- 根区域扫描阶段:G1 GC扫描初始标记阶段标记的幸存者区域,以引用老年代,并标记引用的对象。此阶段与应用程序(不是STW)同时运行,并且必须在开始下一个STW年轻垃圾收集之前完成。
- 并发标记阶段:G1 GC在整个堆中查找可达(活动)对象。此阶段与应用程序同时发生,并且可以被STW年轻垃圾收集中断。
- 重新标记阶段:此阶段为STW收集阶段,帮助完成标记周期。G1 GC耗尽SATB缓冲区,跟踪未访问的活动对象,并执行引用处理。
- 清理阶段:在这个最后阶段,G1 GC执行核算和RSet清理的STW操作。在核算期间,G1 GC识别完全空闲的区域和混合垃圾收集候选区域。当清理阶段重置并将空区域返回到空闲列表时,它是部分并发的。
5. 重要的默认值 Important Defaults
G1 GC是一个具有默认值的自适应垃圾收集器,使其能够在不修改的情况下高效工作。表10-1“G1垃圾收集器重要选项的默认值”列出了重要选项及其在Java HotSpot VM中的默认值,构建24。通过在JVM命令行上更改设置,输入表10-1“G1垃圾收集器重要选项的默认值”中的选项,您可以调整和调优G1 GC以满足您的应用程序性能需求。
Table 10-1 Default Values of Important Options for G1 Garbage Collector
表10-1 G1垃圾收集器重要选项默认值
Option and Default Value | Option |
---|---|
-XX:G1HeapRegionSize=n | 设置G1区域大小。该值将是2的n次幂,范围从1 MB到32 MB。基于最小Java堆大小,目标是拥有大约2048个区域。(2G-64G) |
-XX:MaxGCPauseMillis=200 | 设置所需的最大停顿时间的目标值。默认值是200毫秒。指定的值与堆大小不匹配。 |
-XX:G1NewSizePercent=5 | 设置用作年轻代大小的最小堆百分比。默认值是Java堆的5%。指定的值与堆大小不匹配。这是一个实验性的标识。请查看How to Unlock Experimental VM Flags部分的解锁方式。 该设置将替换-XX:DefaultMinNewGenPercent设置。 |
-XX:G1MaxNewSizePercent=60 | 设置用作年轻代大小的最大堆大小的百分比。默认值是Java堆的60%。指定的值与堆大小不匹配。这是一个实验性的标识。请查看How to Unlock Experimental VM Flags部分的解锁方式。 该设置将替换-XX:DefaultMaxNewGenPercent设置。 |
-XX:ParallelGCThreads=n | 设置STW工作线程的值。将n的值设置为逻辑处理器的数量。n的值与逻辑处理器数相同,最大值为8。如果有超过8个逻辑处理器,则将n的值设置为大约5/8个逻辑处理器。这在大多数情况下都是可行的,除了较大的SPARC系统,其中n的值大约是逻辑处理器的5/16。 |
-XX:ConcGCThreads=n | 设置并行标记线程的数量。设置n为并行垃圾收集线程数(ParallelGCThreads)的大约1/4。 |
-XX:InitiatingHeapOccupancyPercent=45 | 设置触发标记周期的Java堆占用阈值。默认占用整个Java堆的45%。 |
-XX:G1MixedGCLiveThresholdPercent=85 | 为混合垃圾收集周期中包含的年老区域设置占用阈值。默认活跃率为85%。这是一个实验性的标识。请查看How to Unlock Experimental VM Flags部分的解锁方式。 该设置将替换-XX:G1OldCSetRegionLiveThresholdPercent 设置。 |
-XX:G1HeapWastePercent=5 | 设置您愿意浪费的堆的百分比。当可回收垃圾百分比小于堆垃圾百分比时,Java HotSpot VM不会启动混合垃圾回收周期。默认是5%。 |
-XX:G1MixedGCCountTarget=8 | 设置标记周期后混合垃圾收集的目标数量,以收集最多为G1MixedGCLIveThresholdPercent实时数据的年老区域。默认值是8个混合垃圾收集。混合集合的目标是在这个目标数量内。 |
-XX:G1OldCSetRegionThresholdPercent=10 | 设置在混合垃圾收集周期中收集的年老区域数量的上限。默认值是Java堆的10%。 |
-XX:G1ReservePercent=10 | 设置保持空闲的预留内存百分比,以降低空间溢出的风险。默认是10%。当您增加或减少百分比时,请确保以相同的数量调整总Java堆。 |
6. 如何解锁虚拟机实验标志位 How to Unlock Experimental VM Flags
要更改实验标志的值,必须先解锁它们。可以通过在命令行中任何实验标志之前显式地设置-XX:+UnlockExperimentalVMOptions来实现这一点。例如:
java -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=10 -XX:G1MaxNewSizePercent=75 G1test.jar
7. 建议 Recommendations
当您评估和微调G1 GC时,请记住以下建议:
-
年轻代大小:避免使用-Xmn选项或任何或其他相关选项(如-XX:NewRatio)显式设置年轻代大小。固定年轻代的大小会覆盖目标暂停时间目标。
-
暂停时间目标:在评估或调优任何垃圾收集时,总是要权衡延迟和吞吐量。G1 GC是一个具有统一停顿的增量垃圾收集器,但应用程序线程的开销也更大。
G1 GC的吞吐量目标是90%的应用程序时间和10%的垃圾收集时间。将其与Java HotSpot VM并行收集器进行比较。并行收集器的吞吐量目标是99%的应用程序时间和1%的垃圾收集时间。
因此,当您评估G1 GC的吞吐量时,请放宽停顿时间目标。设置过于激进的目标表明您愿意承担垃圾收集开销的增加,这将直接影响吞吐量。当您评估G1 GC的延迟时,您设置了所需的(软)实时目标,G1 GC将尝试满足它。作为副作用,吞吐量可能会受到影响。有关更多信息,请参见垃圾优先垃圾收集器中的停顿时间目标一节。 -
改良混合垃圾收集:在调优混合垃圾收集时,请试验以下选项。有关这些选项的信息,请参阅重要默认值部分:
-XX:InitiatingHeapOccupancyPercent: 用于更改标记阈值。(45%) -XX:G1MixedGCLiveThresholdPercent and -XX:G1HeapWastePercent: 用于更改混合垃圾收集决策。(85%,5%) -XX:G1MixedGCCountTarget and -XX:G1OldCSetRegionThresholdPercent: 用于为旧区域调整CSet。(8,10%)
8. 溢出和耗尽日志消息 Overflow and Exhausted Log Messages
当您在日志中看到空间溢出或空间耗尽的消息时,G1 GC没有足够的内存用于幸存者对象或提升对象,或用于两者。因为Java堆已经达到最大。示例消息:
924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space exhausted), 0.1957310 secs]
924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space overflow), 0.1957310 secs]
为了缓解这个问题,可以尝试以下调整:
- 增加-XX:G1ReservePercent选项的值(以及相应的总堆),以增加“to-space”的预留内存量。(默认10%)
- 通过减少-XX:InitiatingHeapOccupancyPercent的值,提前开始标记周期。(默认45)
- 增加-XX:ConcGCThreads选项的值,以增加并行标记线程的数量。
有关这些选项的描述,请参阅重要默认值部分。
9. 巨型对象和巨大的分配 Humongous Objects and Humongous Allocations
对于G1 GC,任何超过半个区域大小的对象都被认为是巨型对象。这样的对象在老一代中被直接分配到巨大的区域。这些巨大的区域是一系列连续的区域。StartsHumongous标志着连续集合的开始,ContinuesHumongous标志着该集合的连续。
在分配任何巨大的区域之前,检查标记阈值,如果需要,启动并发循环。
在清理阶段和整个垃圾收集周期的标记周期结束时释放死的巨型对象。
为了减少复制开销,在任何疏散停顿中都不包含巨大的对象。一个完整的垃圾收集周期将巨型对象压缩在合适的位置 。
因为StartsHumongous和ContinuesHumongous区域的每个单独集合只包含一个巨型对象,巨型对象的末端和该对象所跨越的最后一个区域的末端之间的空间是未使用的。对于仅略大于堆区域大小倍数的对象,这些未使用的空间可能导致堆碎片化。
如果您看到由于巨大的分配而启动的连续并发周期,并且如果这样的分配正在分割您的老年代,那么增加-XX:G1HeapRegionSize的值,这样以前的巨大对象将不再是巨大的,并将遵循常规分配路径。