默认参数
-
最大GC线程数取决于堆大小与CPU资源
-
初始堆大小是物理内存的 1/64
-
最大堆大小是物理内存的 1/4
调优目标
JVM的垃圾收集器配置为优先满足两个目标之一:最大暂停时间和吞吐量。如果主要目标得到满足,GC将尝试最大限度地满足另一个目标。当然,这些目标不可能总是被满足。应用程序需要一个最小的堆来容纳所有的实时数据,而其他参数可能会阻止一些目标的达成。
最大暂停时间
暂停时间是指GC停止应用并回收不再使用的内存时间。最大暂停时间目标的意图是限制这些暂停的最长时间。
最大停顿时间目标是通过命令行选项 `-XX:MaxGCPauseMillis=<nnn>` 指定的。这被解释为对GC的配置,即希望停顿时间为<nnn>毫秒或更少。垃圾收集器会调整Java堆的大小和其他与GC有关的参数,试图使垃圾收集暂停时间短于<nnn>毫秒。最大停顿时间目标的默认值因收集器而异。这些调整可能会导致GC更频繁地发生,减少应用程序的整体吞吐量。
吞吐量目标
吞吐量目标是以收集垃圾的时间来衡量的,而垃圾收集之外的时间是应用时间。
该目标由命令行选项 `-XX:GCTimeRatio=nnn` 指定。垃圾收集时间与应用时间的比例是1/(1+nnn)。例如, `-XX:GCTimeRatio=19` 设定的目标是垃圾收集时间的1/20或总时间的5%。
垃圾收集的时间是所有垃圾收集引起的暂停的总时间。如果没有达到吞吐量的目标,那么垃圾收集器的一个可能的行动是增加堆的大小,以便在收集暂停之间的应用中花费的时间可以更长。
如果吞吐量和最大暂停时间的目标已经得到满足,那么垃圾收集器就会减少堆的大小,直到其中一个目标(无一例外是吞吐量目标)无法得到满足。垃圾收集器可以使用的最小和最大的堆尺寸可以分别用 `-Xms=<nnn>` 和 `-Xmx=<mmm>` 来设置最小和最大堆尺寸。
调优策略
-
不要为堆选择最大值,除非您知道需要一个大于默认最大堆大小的堆。选择一个足以满足您的应用程序的吞吐量目标。
-
如果堆增长到其最大大小并且未满足吞吐量目标,则最大堆大小对于吞吐量目标来说太小了。将最大堆大小设置为接**台上总物理内存的值,但不会导致应用程序交换。再次执行应用程序。如果仍然没有达到吞吐量目标,则应用时间的目标对于*台上的可用内存来说太高了。
-
如果可以达到吞吐量目标,但暂停时间太长,则选择最大暂停时间目标。选择最大暂停时间目标可能意味着无法满足您的吞吐量目标,因此请选择应用程序可接受的折衷值。
-
当垃圾收集器试图满足竞争目标时,堆的大小通常会发生波动。即使应用程序已达到稳定状态,也是如此。实现吞吐量目标(可能需要更大的堆)的压力与最大暂停时间和最小占用空间(两者都可能需要小堆)的目标相竞争。
分代收集器
x 轴显示对象生命周期。y 轴上的字节数是具有相应生命周期的对象字节数。
影响GC的因素
总堆大小
在虚拟机的初始化中,内存为堆保留了全部空间。保留空间的大小可以用 `-Xmx` 选项来指定。如果 `-Xms` 参数的值小于 `-Xmx` 参数的值,那么并非所有被保留的空间都会立即提交给虚拟机。未提交的空间在这个图中被标记为 "虚拟"。堆的不同部分,也就是老一代和年轻一代,可以根据需要增长到虚拟空间的极限。
一些参数是堆的一个部分与另一个部分的比例。例如,参数 `-XX:NewRatio` 表示老一代与年轻一代的相对大小。
年轻代
在总可用内存之后,影响垃圾收集性能的第二大因素是专用于年轻代的堆的比例。
年轻代越大,minor gc发生的频率就越低。然而,对于有堆大小的限制,较大的年轻代意味着较小的老年代,这将增加major gc的频率。最佳选择取决于应用程序分配的对象的生命周期分布。
年轻代大小选项
默认情况下,年轻代大小由选项 `-XX:NewRatio` 控制。
比如设置 `-XX:NewRatio=3` 表示年轻代和年老代的比例为 1:3。换句话说,eden区和survivor区的总大小将是总堆大小的四分之一。
选项 `-XX:NewSize` 和 `-XX:MaxNewSize` 限制了年轻代的大小。将这些设置为相同的值可以固定年轻代,就像将 `-Xms` 和 `-Xmx` 设置为相同的值可以固定总堆大小一样。这对于以比 `-XX:NewRatio` 允许的整数倍更细的粒度调整年轻代很有用。
幸存者空间大小
您可以使用选项 `-XX:SurvivorRatio` 来调整幸存者空间的大小,但这通常对性能并不重要。
如果幸存者空间太小,那么会直接溢出到老年代。如果幸存者空间太大,那么它们就是无用的空间。在每次垃圾回收时,虚拟机都会选择一个阈值数,即对象进入老年代之前可以复制的次数。选择这个阈值是为了让survivor保持半满。可以使用日志配置 -Xlog:gc,age 来显示这个阈值和新生代对象的年龄。它对于观察应用程序的生命周期分布也很有用。
以下是服务器应用程序的一般准则:
-
首先确定您可以为虚拟机提供的最大堆大小。 然后,针对年轻代大小绘制性能指标以找到最佳设置。
-
请注意,最大堆大小应始终小于机器上的内存,以避免过多的页面错误和抖动。
-
如果总堆大小是固定的,那么增加年轻代大小需要减少老年代大小。 保持老年代足够大,以容纳应用程序在任何给定时间使用的所有实时数据,再加上一些空闲空间(10% 到 20% 或更多)。
-
受之前对老年代的约束:
-
给年轻代足够的内存。
常见的垃圾收集器
串行收集器
串行收集器使用一个单线程来执行所有的垃圾收集工作,相对高效,因为没有线程之间的开销。它最适合于单处理器机器,可以通过选项-XX:+UseSerialGC启动。
并行收集器
并行收集器也被称为吞吐量收集器。串行收集器和并行收集器的主要区别是,并行收集器有多个线程,用于加速垃圾收集,可以通过使用-XX:+UseParallelGC选项来启动。
并发收集器
-
G1垃圾收集器:这种收集器适用于有大量内存的多处理器机器。它满足GC暂停时间的目标,同时实现高吞吐量。JDK11默认收集器,可以使用-XX:+UseG1GC启动。
-
CMS收集器:该收集器适用于较短的GC暂停时间。使用选项-XX:+UseConcMarkSweepGC来启动CMS收集器。从JDK 9开始,CMS收集器已被废弃。
ZGC垃圾收集器
ZGC是一个低延迟垃圾收集器。ZGC并发地执行所有任务,而不停止应用程序线程的执行。
ZGC是为那些需要低延迟(少于10毫秒的停顿)和使用非常大的堆(TB级)的应用而设计的。可以通过使用-XX:+UseZGC选项来启动。
ZGC作为一项实验性功能,从JDK 11开始提供。
选择垃圾收集器
除非应用程序有相当严格的暂停时间要求,否则使用JVM默认的收集器。
调整堆的大小以提高性能。如果性能仍然不能满足要求,则按照如下指南选择:
-
如果应用程序将在单个处理器上运行,并且没有暂停时间的要求,那么选择带有选项-XX:+UseSerialGC的串行收集器。
-
如果应用程序的峰值性能是首要任务,并且没有暂停时间要求,或者可以接受一秒钟或更长的暂停时间,那么就使用JVM默认GC,或者用-XX:+UseParallelGC选择并行收集器。
-
如果响应时间比总体吞吐量更重要,而且垃圾收集的暂停时间必须保持在大约一秒钟以内,那么就用-XX:+UseG1GC或-XX:+UseConcMarkSweepGC选择一个并发的收集器。
-
如果响应时间是一个高优先级,并且有一个非常大的堆,则使用-XX:UseZGC。
这些指南只提供了一个选择收集器的参考,因为性能取决于堆的大小、实时数据量,以及可用处理器的数量和速度。
如果推荐的收集器不能达到预期的性能,那么首先尝试调整堆和生成器的大小,以满足预期的目标。如果性能仍然不足,那么就尝试另一个收集器。使用并发收集器来减少暂停时间,并使用并行收集器来增加多处理器硬件上的总体吞吐量。
总结
-
应将 `-Xms` 与 `-Xmx` 设置为同一值,无论扩展还是缩小空间都会进行Full GC。
-
`-Xmx` 需要根据系统的配置来确定,要给操作系统和JVM本身留下一定的剩余空间。 推荐配置系统或容器里可用内存的 70-80% 最好。
-
`-Xmn` 可以方便指定新生代空间的初始值和最大值。
-
Xms, -Xmx:在堆大小上放置边界以增加垃圾收集的可预测性。副本服务器中的堆大小受到限制,因此即使 Full GC 也不会触发 SIP 重传。Xms设置起始大小以防止堆扩展引起的暂停。
-
XX:+UseG1GC:使用垃圾优先 (G1) 收集器。
-
XX:MaxGCPauseMillis:设置最大 GC 暂停时间的目标。这是一个软目标,JVM 将尽最大努力实现它。
-
XX:ParallelGCThreads:设置垃圾收集器并行阶段使用的线程数。默认值因运行 JVM 的*台而异。
-
XX:ConcGCThreads:并发垃圾收集器将使用的线程数。默认值因运行 JVM 的*台而异。
-
XX:InitiatingHeapOccupancyPercent:启动并发 GC 周期的(整个)堆占用百分比。基于整个堆的占用率而不只是其中一代(包括 G1)的占用率触发并发 GC 周期的 GC 使用此选项。值 0 表示“执行恒定的 GC 循环”。默认值为 45。
G1 垃圾收集器
简介
G1垃圾收集器是针对拥有大量内存的多处理器机器。它试图满足垃圾收集暂停时间的目标,同时使用较少的配置实现高吞吐量。G1旨在利用当前的目标应用和环境,在延迟和吞吐量之间提供最佳的*衡,其特点包括:
-
堆的大小可达10GB或更大,超过50%的Java堆被实时数据占据。
-
对象分配的速度随时间变化很大。
-
堆中存在大量的碎片。
-
可预测的停顿时间目标,不超过几百毫秒,避免长时间的垃圾收集停顿。
-
G1取代了并发标记-扫描(CMS)收集器。它也是默认的收集器。
基本概念
-
分代:与其他收集器类似,G1将堆分成年轻代和老年代。GC工作集中在年轻代。
-
并行:有些操作总是在STW中进行,以提高吞吐量。其他一些需要更多时间的操作,如全局标记等整堆操作,则与应用程序并行进行。G1通过跟踪以前的应用行为和垃圾收集暂停的信息来建立模型,从而实现可预测性。它使用这些信息来确定暂停时所做工作的大小。例如,G1首先在最有效的区域回收空间(也就是主要被垃圾填满的区域)。
-
转移(Evacuation):G1主要通过转移(Evacuation)来回收空间:在选定的内存区域内发现的要收集的活对象被复制到新的内存区域,在此过程中压缩它们。转移完成后,之前被活对象占据的空间被应用程序重新用于分配。
G1 也是一个分区的垃圾回收算法,G1 的老年代和年轻代不再是一块连续的空间,整个堆被划分成若干个大小相同的 Region,也就是区。Region 的类型有 Eden、Survivor、Old、Humongous 四种,而且每个 Region 都可以单独进行管理。
Humongous 是用来存放大对象的,如果一个对象的大小大于一个 Region 的 50%(默认值),那么我们就认为这个对象是一个大对象。为了防止大对象的频繁拷贝,我们可以将大对象直接放到 Humongous 中。
对象会在 Eden Regions 中分配,当进行年轻代 GC 时,会将活跃对象拷贝到 Survivor Regions;当对象年龄超过晋升阈值时,就把活跃对象复制进 Old Regions。
回收周期
在宏观层面上,G1收集器在两个阶段之间交替进行。纯年轻代阶段包含垃圾收集,用老一代的对象逐渐填满当前可用的内存。空间回收阶段是G1在处理年轻代的同时,逐步回收老年代的空间。然后,循环重新开始,进入只处理年轻代的阶段。
下面详细描述了G1垃圾收集周期的各个阶段、它们的停顿和各阶段之间的转换。
-
纯年轻阶段。这个阶段以几个正常的young gc开始,将对象升级到老年代。当老年代占用率达到某个阈值,即启动堆占用率阈值(the Initiating Heap Occupancy threshold)时,纯年轻阶段和空间回收阶段之间的过渡就开始了。在这个时候,G1会并发启动young gc,而不是普通young gc。
-
并发启动:这种类型的gc除了执行普通young gc外,还启动标记过程。并发标记确定老年代区域中所有当前可到达的(存活)对象,并将其保留到接下来的空间回收阶段。当收集标记还没有完全完成时,普通young gc可能发生。标记工作以两个特殊的STW来完成。最终标记和清理。
-
最终标记。此阶段完成最终标记,执行了全局引用处理和类的卸载,回收了完全空的区域并清理了内部数据结构。在最终标记和清理阶段之间,G1计算信息,以便以后能够在选定的老一代区域并发回收空闲空间,这将在Cleanup阶段中最终完成。
-
清理。这个暂停决定了空间回收阶段是否会实际跟进。如果接下来是空间回收阶段,纯年轻阶段将以单线程Prepare Mixed young gc完成。
-
空间回收阶段。该阶段由多个mixed GC组成,除了年轻代区域外,还转移了老年代区域的对象。当G1确定转移更多的老年代区域不会产生足够的空间时,空间回收阶段结束。
在空间回收之后,回收周期以另一个纯年轻阶段重新开始。作为兜底策略,如果应用程序在gc时耗尽了内存,G1会像其他收集器一样执行Full GC。
调优参数
-
-XX:+InitiatingHeapOccupancyPercent(简称 IHOP):G1 内部并行回收循环启动的阈值,默认为 Java Heap 的 45%。这个可以理解为老年代使用大于等于 45% 的时候,JVM 会启动垃圾回收。这个值非常重要,它决定了在什么时间启动老年代的并行回收。
-
-XX:MaxGCPauseMills:预期 G1 每次执行 GC 操作的暂停时间,单位是毫秒,默认值是 200 毫秒,G1 会尽量保证控制在这个范围内。
-
-XX:G1NewSizePercent (默认:5) Young region 最小值 -XX:G1MaxNewSizePercent (默认: 60) Young region 最大值
-
正常来说,大部分在 Young 的对象都不会存活很长时间。如果不符合这个规则 (大部分在 Young 的对象都不会存活很长时间),你可能需要调整一下 Young 区域占比。来降低 Young 对象的拷贝时间。