java GC Collector

java 垃圾收集器

# 最小堆值,不设置默认为操作系统 1/64
-Xms4G
# 最大堆值,不设置默认为操作系统 1/4
-Xmx4G

G1

jdk9开始默认启用,显示启用 -XX:+UseG1GC

介绍

  1. 服务器级计算机上的垃圾优先 (G1) 收集器,否则为串行收集器。
  2. GC 线程的最大数量受堆大小和可用 CPU 资源的限制
  3. 初始堆大小为物理内存的 1/64
  4. 最大堆大小为物理内存的 1/4
  5. 分层编译器,同时使用 C1 和 C2

常用配置方式(单选)

首先避免 -Xmn-XX:NewRatio 设置,因为年轻代大小是 G1 满足最大暂停时间期望的主要手段。
从其它GC换成G1的时候,先删除除-Xmx-Xms-XX:MaxGCPauseMillis以外的所有配置。

最大暂停时间期望与吞吐量期望(二选一)

  1. 最大暂停时间期望
# 高吞吐期望应用可以配高点,或者增加堆大小
-XX:MaxGCPauseMillis=<nnn>

-XX:MaxGCPauseMillis=12 表示期望垃圾收集器的STW时间小于等于 12 毫秒。垃圾收集器动态调整 Java 堆大小和与垃圾收集相关的其他参数,以尝试使垃圾收集暂停时间短于 12 毫秒。最大暂停时间目标的默认值因收集器而异。这些调整可能会导致垃圾收集更频繁地发生,从而降低应用程序的整体吞吐量。但在某些情况下,无法达到所需的暂停时间目标。

  1. 吞吐量期望
# 垃圾收集时间与应用程序时间的比率为 1/(1+nnn)
-XX:GCTimeRatio=<nnn>

-XX:GCTimeRatio=19 表示期望垃圾收集总时间为应用程序运行时间的 1/20 。
垃圾收集所花费的时间是所有垃圾收集引起的暂停的总时间。如果未达到吞吐量目标,则垃圾收集器可能采取的操作之一是增加堆的大小,以便在收集暂停之间应用程序所花费的时间可以更长。

调优路线

优化Full GC

gc+heap=info 查看大对象区GC情况 "Humongous regions: X->Y"

  1. 确定大对象占用空间 -XX:G1HeapRegionSize
  2. 增加堆空间(同时也会增加region标记时间)
  3. 通过显式设置增加并发标记线程数 -XX:ConcGCThreads
  4. 强制G1提前开始region标记。
    G1 根据应用程序早期运行情况自动确定Initiating Heap Occupancy Percent (IHOP) 阈值。 如果应用程序行为发生变化,这些预测可能是错误的。可以通过以下两种方式修复
# 增加 IHOP 自适应计算中使用的缓冲区,降低何时开始空间回收的目标占用率
-XX:G1ReservePercent
# 禁用 IHOP 的自适应计算
-XX:-G1UseAdaptiveIHOP  -XX:InitiatingHeapOccupancyPercent

大对象与内存碎片冲突问题

在java中对象分配内存需要一个连续的空间。如果找不到空闲连续空间则回发生 fullGC。

  1. 配置XX:G1HeapRegionSize,增加单个region大小,让大对象在一个region就可以分配
  2. 配置-Xmx,增加堆内存大小

优化延时问题

系统异常和 real-time 太长

gc+cpu=info查看GC耗时,正常案例如右所示: User=0.19s Sys=0.00s Real=0.01s

Sys太长
  1. -Xmx等于-Xms,并且设置-XX:+AlwaysPreTouch,避免JVM与操作系统的内存交换。
  2. 可能由于透明大页面的使用造成,毙之。
  3. 后台应用占用了IO资源,导致GC的时候Sys耗时过长,可以尝试配置单独文件系统。
Real太长
  1. cpu资源不足,java程序可能跑在一个CPU已经跑满的操作系统上面

引用对象处理时间太长

有关引用对象处理所花费时间的信息显示在 Reference Processing 中。-XX:ReferencesPerThread=N配置每N个引用对象启动一个子线程手机处理,线程数上限由-XX:ParallelGCThreads 控制。默认为0表示使用可用线程处理,或者通过配置-XX:-ParallelRefProcEnabled禁用引用对象并行处理。

年轻代收集时间太长

通过降低-XX:G1NewSizePercentXX:G1MaxNewSizePercent减少年轻代大小,但有可能造成频繁年轻代GC。

混合(年轻代+老年代)收集耗时太长

年轻代收集时间太长看上一个方案,老年代有以下三种方式尝试

  1. 将老年代区域回收分散到更多垃圾收集中,调大-XX:G1MixedGCCountTarget配置。
  2. 配置-XX:G1MixedGCLiveThresholdPercent,不将耗时过长的region放入老年代收集目标内。
  3. 尽早停止老年代收集,调大-XX:G1HeapWastePercent配置。

巨量region合并和堆Roots可达性分析耗时(原文:High Merge Heap Roots and Scan Heap Roots Times)

  1. 减少region数量,调大-XX:G1HeapRegionSize配置
  2. 虚假的高扫描堆根时间与分配大对象的应用程序相结合可能是由尝试通过批处理减少并发记忆集更新工作的优化引起的。如果创建此类批处理的应用程序发生在垃圾回收之前,则可能会对合并堆根时间产生负面影响。使用-XX:-ReduceInitialCardMarks禁用此优化并可能避免这种情况。

频繁GC

G1默认允许频繁GC,-XX:GCPauseIntervalMillis默认值仅仅略高于 -XX:MaxGCPauseMillis,觉得GC太频繁了,可以适当调整-XX:GCPauseIntervalMillis 大小

调整吞吐量配置

G1的默认策略试图保持吞吐量和延迟之间的平衡。

  1. 配置最大暂停时间,增加吞吐量与延迟 -XX:MaxGCPauseMillis
  2. 在存在剩余空间的前提下,增加新生代大小 -XX:G1NewSizePercent-XX:G1MaxNewSizePercent
  3. 减少GC并发工作量 XX:G1RSetUpdatingPauseTimePercent

调整堆大小配置

其它默认值配置

选项和默认值 描述
-XX:+ReduceInitialCardMarks 这会将初始对象分配的并发记忆集更新(细化)工作一起批处理。
-XX:+ParallelRefProcEnabled -XX:ReferencesPerThread=1000 -XX:ReferencesPerThread=N决定了并行化的程度:对于每 N 个引用对象,就有一个线程将参与引用处理的子阶段,线程数受-XX:ParallelGCThreads配置限制。设置为0表示将始终使用最大线程数-XX:PalallelGCThreads。这决定了是否处理 java.lang.Ref.* 实例应该由多个线程并行完成
-XX:G1RSetUpdatingPauseTimePercent=10 可以使用此选项控制并发记忆集更新(细化)工作。Refinement 尝试同时安排工作,以便 -XX:G1RSetUpdatingPauseTimePercent最大暂停时间目标的最多百分比用于更新 RS 阶段的垃圾收集暂停,以处理剩余的工作。
-XX:G1SummarizeRSetStatsPeriod=0 这是G1在多少次GC时生成记忆集总结报告的时期。将其设置为零以禁用。生成记忆集摘要报告是一项成本高昂的操作,因此仅应在必要时使用它,并且具有相当高的值。用于 gc+remset=trace打印任何东西。
-XX:GCTimeRatio=12 这是应该花费在垃圾收集而不是应用程序上的目标时间比率的除数。用于确定增加堆之前可用于垃圾回收的目标时间比例的实际公式为 1 / (1 + GCTimeRatio)。此默认值导致目标大约有 8% 的时间花在垃圾收集上。
-XX:G1PeriodicGCInterval=0 G1 定期垃圾收集的时间间隔(以毫秒为单位)。设置为零以禁用。
-XX:+G1PeriodicGCInvokesConcurrent 如果设置,定期垃圾收集会触发并发标记或继续现有的收集周期,否则会触发 Full GC。
-XX:G1PeriodicGCSystemLoadThreshold=0.0 主机调用返回的当前系统负载的阈值 getloadavg(),以确定是否应触发定期垃圾收集。当前系统负载高于此值会阻止定期垃圾收集。值为零表示禁用此阈值检查。

ZGC

介绍

高资源占用,低延时。支持范围为几百兆到16T的内存。是一种可扩展的低延迟垃圾收集器。ZGC 同时执行所有昂贵的工作,而不会停止应用程序线程的执行超过一毫秒。适合需要低延迟的应用。暂停时间与正在使用的堆大小无关。ZGC 可以很好地处理从几百兆字节到 16TB 的堆大小。

ZGC 被设计为自适应且需要最少的手动配置。在 Java 程序执行期间,ZGC 通过调整代大小、扩展 GC 线程数量以及调整保有阈值来动态适应工作负载。主要的调整旋钮是增加最大堆大小。

使用命令

# 使用非分代ZGC(最早版本不支持分代)
-XX:+UseZGC
# 使用分代ZGC(官方推荐)
-XX:+UseZGC -XX:+ZGenerational

常用基础配置

设值最大堆(最重要)

# 指定最大堆为8g
-Xmx8g
# 指定最大堆为5g,通常4g就开始GC,实在不行才会跑的5g,再不行就OOM
-Xmx5g -XX:SoftMaxHeapSize=4gZGC

禁用未用内存释放

默认情况下,ZGC会将不需要用的内存释放给操作系统。该功能对于外部程序都有意义,但是会对内部线程延长产生负面影响。

# 禁用未用内存释放
-XX:-ZUncommit
# 由于最大堆和最小堆大小一致,也不会释放未用内存给操作系统
-Xmx8g -Xms8g
# 未用内存返给操作系统延迟。默认 300 秒
-XX:ZUncommitDelay=<seconds>
# 极低延迟系统建议配置最大堆等于最小堆值,并启动在应用程序之前内存分页
-Xmx32g -Xms32g -XX:+AlwaysPreTouch

Using Large Pages(使用大页面)

在 Linux x86 上,大页面(huge page)的大小为 2MB。假设堆大小为16G。则表示需要16GB / 2MB = 8192 个大页面。
使用大页面通常只有好处(吞吐量、延迟、启动时间),没有明显的坏处,但是设置需要root权限,所以默认情况下不启用它。

# 需要root权限,这个也是GC默认不采用ZGC的原因。
echo 9216 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# 如果内核无法找到足够的空闲大页面来满足请求,则上述命令可能失败。而且内核可能需要一些时间来处理请求。在继续后续操作之前,请确认配置已经生效
cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

透明大页面(在 Linux 上,内核 >= 4.7)

使用显式大页面(如前所述)的替代方法是使用透明大页面。对于延迟敏感的应用程序,通常不建议使用透明大页面,因为它往往会导致不必要的延迟峰值。但是,可能值得尝试一下,看看您的工作负载是否或如何受到影响。

# 内核启用madvise模式
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
# ZGC使用shmem大页,如果是ZGC则加上以下配置,别的GC不需要
echo advise > /sys/kernel/mm/transparent_hugepage/shmem_enabled

补充

以下两个设置保证除ZGC外的所有GC都利用透明大页面来优化堆

echo always > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/shmem_enabled

参考地址

https://docs.oracle.com/en/java/javase/21/gctuning/z-garbage-collector.html#GUID-683A80ED-98CF-43A5-86BC-240E2900E53E

posted @ 2024-03-19 14:16  临渊不羡渔  阅读(18)  评论(0编辑  收藏  举报