JVM实战-G1参数调优

发布时间 2023-12-21 18:37:41作者: 鱼007

G1简介

G1 GC,全称Garbage-First Garbage Collector,在JDK1.7中引入了G1 GC,从JAVA 9开始,G1 GC是默认的GC算法。通过-XX:+UseG1GC参数来启用。
G1收集器有分区概念,是工作在堆内不同分区上的收集器。G1的分区既可以是年轻代也可以是老年代,同一个代的分区不需要连续。
G1收集器在运行过程中,会自己调整新生代和老年代的Region数量,主要是通过adapt代的大小来调整对象晋升的速度和年龄,从而达到为收集器设置的暂停时间目标,因此不建议手动设置新生代和老年代的大小,只要设置整个堆的大小即可。
G1对Region的回收是基于复制算法进行的:将一个Region内的存活对象放入另一个Region中,再把这个Region的垃圾对象给清理掉,这样就不会产生内存碎片。

G1可以解决的场景包括:

堆大小可达数十 GB 或更大,超过 50% 的 Java 堆被实时数据占用。
对象分配和提升的速率可能会随时间而显著变化。
堆中存在大量碎片。
可预测的暂停时间目标目标不超过几百毫秒,避免长时间的垃圾回收暂停。

G1是设计用来取代CMS的,与CMS相比,G1有以下优势:

超大堆的表现更出色(CMS的STW时间会过长)
配置更少(G1具备一定的弹性调整能力)
在预测停顿时间上更稳定(CMS是全局扫描标记,G1是针对分区)
避免了CMS的垃圾碎片(G1是针对分区,使用复制算法)

G1相关参数配置

#使用G1垃圾收集器,在低延迟和高吞吐间寻找平衡,可以调整最大停止时间,设置新生代大小来提高吞吐量
-XX:+UseG1GC

#堆内存,示例设置最大最小值为4g,对于G1,一般建议2g以上。注意设定Xms=Xmx,防止发生扩容、缩容
-Xms4g -Xmx4g 

#配置元空间初始256m、最大256m。不配置的话,元空间会不受限地占用物理机内存
-XX:MetaspaceSize=256m  -XX:MaxMetaspaceSize=256m 
 
#设置最大暂停时间,默认200ms。G1收集回收器将堆进行分区,划分为一个个的区域,每次收集的时候,只收集其中几个区域,以此来控制垃圾回收产生一次停顿时间。
#暂停时间只是一个目标,并不能总是得到满足,G1会逐步调整到最佳状态。但若暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度,最终退化成Full GC。
#在极端环境(如要求业务接口需要快速返回结果)下可尝试设置20ms以下,此时建议:精简下应用服务(减少实例的产生)、升级高版本jdk(17)、做好压测、长期关注。
-XX:MaxGCPauseMillis=100

#指定Region大小,必须是2次幂,最大是32m,具体取值有1MB、2MB、4MB、8MB、16MB、32MB。注意当对象占用内存超过Region的一半时将被视为大对象,被分配到Humongous区域
#可以预估应用服务的对象大小,确定Region大小。如果没有大对象的场景,则可尝试配置2m、1m,使单个Region能被快速回收
#该配置非必填,不声明时,Region大小等于堆大小除以2048
-XX:G1HeapRegionSize=2m

#针对混合回收回收的参数:混合回收不仅针对老年代,还有新生代和大对象
#老年代Region触发混合GC的占比,默认值是45,也就是说老年代占据了堆内存45%的Region的时,会触发混合GC。该值一般不需要调整,这样可以让JVM内存占用维持在50%左右
-XX:InitiatingHeapOccupancyPercent=45
#混合回收阶段会执行8次(默认值),一次只回收掉部分Region,然后系统继续运行一小段时间,之后再继续混合回收,重复8轮。混合回收通过间断操作,可以把每次的回收时间控制在指定的停顿时间之内,最终也达到了垃圾清理的效果。
-XX:G1MixedGCCountTarget=8
#混合回收整理出来的空闲空间占heap的5%时(默认值),终止本次回收
-XX:G1HeapWastePercent=5

#如果一个Region中的存活对象大于Region大小的85%的话(默认值),就不去回收这个Region
-XX:G1MixedGCLiveThresholdPercent=85

#设置新生代大小,默认5%,默认最大60%。在运行过程中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过G1MaxNewSizePercent值
#年轻代中的Eden和Survivor对应的region也跟之前 一样,默认8:1:1,例如年轻代现在有1000个region,eden区对应800个,s0对应100个,s1对应 100个
#理论上,不同的应用、不同的CPU硬件资源,都会有不同的最优值,但区别不会太大。通过实践总结,调整该值的收益远不如调整其他参数(如上面提到的几个)
-XX:G1NewSizePercent=5 -XX:G1MaxNewSizePercent=60

 #gc日志打印到执行日志文件
-Xloggc:/data/server/${app}/gc.log
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime 

#可以生成更详细的Survivor空间占用日志,开发环境调试用
-XX:+PrintAdaptiveSizePolicy 
#在控制台输出GC情况,一般是本地调试用。(上线后是后台运行,不关注控制台日志)
-verbose:gc 

#开启远程debug,开发环境、测试环境可考虑配置
-Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n 

#spring相关,指定加载配置文件
--spring.config.location=classpath:/,classpath:/config/,file:./,file:./config/,file:/home/mall-job/conf/

示例1、高并发系统jvm参数配置
-Xms8g -Xmx8g
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1HeapRegionSize=8m -XX:G1MixedGCCountTarget=8
-Xloggc:/data/server/${app}/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime

示例2、高响应系统jvm参数配置示例(尽量提高业务吞吐量),核心思想:通过控制Region的大小,从而加快单个Region的回收耗时
-Xms2g -Xmx2g
-XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:G1HeapRegionSize=1m -XX:G1MixedGCCountTarget=10
-Xloggc:/data/server/${app}/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime

备注:其他JVM参数(-XX)

#新时代E区和S区比例,默认8:1:1。一般来说,不需要调整
-XX:SurvivorRatio=8 

#异常场景相关参数 
-XX:ErrorFile=文件 设置错误日志路径 -XX:ErrorFile=./hs_err_pid%p.log %p为当前进程号
-XX:OnError=命令 错误发生时执行命令 -XX:OnError="gcore %p;dbx - %p"
-XX:OnOutOfMemoryError=命令 内存溢出时执行命令
-XX:MaxDirectMemorySize=size 设置直接内存最大值 -XX:MaxDirectMemorySize=100m 默认为0 当直接内存达到设置的最大值会FullGC
-XX:ObjectAlignmentInBytes=alignment 设置java对象的内存对齐,默认是8字节
-XX:ThreadStackSize 设置线程栈大小 -XX:ThreadStackSize=1m = -Xss
-XX:-UseBiasedLocking 禁用偏向锁 默认开启,不禁用 如果使用的是大量的没有竞争的同步,使用偏向锁会提升性能
-XX:-UseCompressedOops 禁用压缩指针堆内存小于32G时默认开启 开启后,对象引用是32位而不是64位,可以提升性能。只有64位的jvm才生效
-XX:+DoEscapeAnalysis 开启逃逸分析 默认开启
-XX:+Inline 开启方法内联 默认开启
-XX:InlineSmallCode=大小 设置应内联的已编译方法的最大代码大小,只有小于此数值的才会内联 默认1000字节
-XX:MaxInlineSize=大小 设置要内联方法的最大字节码大小 默认35字节
-XX:+OptimizeStringConcat 开启字符串连接优化 默认开启
-XX:+PrintInlining 打印方法内联 默认关闭,需和-XX:+UnlockDiagnosticVMOptions 一起使用
-XX:-TieredCompilation 关闭分层编译 默认开启
-XX:+HeapDumpOnOutOfMemoryError  OOM时堆内存dump到当前目录
-XX:HeapDumpPath=路径 设置堆内存dump的路径 -XX:HeapDumpPath=/var/java_pid%p.hprof
-XX:+UnlockDiagnosticVMOptions 开启jvm诊断功能选项

#垃圾回收相关参数
-XX:+AggressiveHeap 开启堆最优化设置 默认关闭
-XX:+CMSClassUnloadingEnabled 当使用CMS垃圾收集器时,允许类卸载 默认开启
-XX:CMSExpAvgFactor=percent 指定垃圾收集消耗的时间百分比 默认这个数是25%,就是25
-XX:CMSInitiatingOccupancyFraction=percent 设置CMS回收开始的老年代百分比 默认-1,任何的负值表示会使用-XX:CMSTriggerRatio选项来定义这个百分比数
-XX:+CMSScavengeBeforeRemark 在CMS重新标记之前执行ygc操作  默认关闭 在remark时间过长时可以开启;开启减少remark的STW时间
-XX:CMSTriggerRatio=percent 设置CMS开始的百分比 默认80,((100 - MinHeapFreeRatio) + (double)( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0=92%
-XX:+UseCMSCompactAtFullCollection  在FULL GC的时候,对年老代的压缩
-XX:CMSFullGCsBeforeCompaction=0  上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩,
-XX:MinHeapFreeRatio=percent GC之后堆内存最小剩余百分比,如果小于此值,则自动扩容 默认40%
-XX:MaxHeapFreeRatio=percent GC之后堆内存最大剩余百分比,如果小于此值,则自动缩容 默认70%
-XX:ParallelGCThreads=threads 设置Parallel GC的线程数 默认根据cpu个数,<=8,则使用8个,>8个3+5N/81台服务器只有1个jvm时使用默认值较好,如果有n个jvm,cpu个数 / n比较合适
-XX:ConcGCThreads=个数 并发GC的线程数 默认值取决于cpu个数 ConcGCThreads = (ParallelGCThreads + 3)/4
-XX:+DisableExplicitGC 使System.gc()显式gc失效 默认不开启,
-XX:G1HeapRegionSize=size 当使用G1收集器时,设置java堆被分割的region大小 1M~32M默认根据堆内存最优化设置
-XX:+G1PrintHeapRegions 打印G1收集器收集的区域  默认关闭
-XX:G1ReservePercent=percent  设置堆内存保留大小,以防晋升失败 0~50 默认10%
-XX:InitialHeapSize=size 堆初始大小 =-Xms
-XX:MaxHeapSize=size 堆最大大小 =-Xmx
-XX:InitialSurvivorRatio=ratio 设置伊甸园区和幸存区初始比例 默认为8:1
-XX:SurvivorRatio=ratio 设置伊甸园区和幸存区比例 默认为8:1  8:1:1
-XX:TargetSurvivorRatio YGC之后,幸存区期望百分比 默认 50%
-XX:InitiatingHeapOccupancyPercent=percent 堆占用达到多少开始并发垃圾回收 只有并发垃圾回收器生效
-XX:MaxGCPauseMillis=time GC最大暂停时间 ms 默认没有最大暂停时间
-XX:MetaspaceSize=size 元空间多次扩容后超过此值就会full gc 默认大约20M 元空间使用本地内存。 尽量设置足够,避免因为这个区域内存不够引发Full GC
-XX:MaxMetaspaceSize=size 设置元空间最大大小 默认不限制 建议和MetaspaceSize一样大,一般256M
-XX:NewSize=size 设置年轻代初始大小 建议年轻代占堆大小的1/4 ~ 1/2
-XX:MaxNewSize=size 设置年轻代最大大小 默认根据最大效能分配
-XX:MaxTenuringThreshold=threshold 最大晋升年龄,从年轻代到老年代 默认:15 - 并行回收器 6 - CMS
-XX:NewRatio=ratio 设置老年代和新生代比例 默认2 老年代 : (伊甸园 + 2个幸存区)
-XX:+PrintGC 打印GC信息 默认关闭
-XX:+PrintGCDetails 打印GC详细信息 默认关闭
-XX:+PrintTenuringDistribution 打印晋升分配
-XX:+ScavengeBeforeFullGC Full gc之前先ygc 默认开启
-XX:+UseTLAB 年轻代使用线程局部缓存 默认开启  效率高
-XX:TLABSize=size 设置初始化thread-local allocation buffer (TLAB)大小
-XX:+UseAdaptiveSizePolicy  JDK 1.8 默认使用 UseParallelGC 垃圾回收器,该垃圾回收器默认启动了 AdaptiveSizePolicy
-XX:+UseParallelGC 年轻代使用并行回收器
-XX:+UseParallelOldGC 老年代使用并行回收器
-XX:+UseParNewGC 为配置CMS,年轻代使用ParNew回收器,CMS会默认开启
-XX:+UseConcMarkSweepGC 使用CMS回收器
-XX:+UseG1GC 使用G1回收器
-XX:+UseGCOverheadLimit 限制GC的运行时间,通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生 并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作
-XX:+UseStringDeduplication 开启字符串去重 G1回收器生效
-XX:AutoBoxCacheMax=20000 加大Integer Cache
-XX:+PrintPromotionFailure 知道是多大的新生代对象晋升到老生代失败从而引发Full GC时的。

补充:G1工作过程简述

G1垃圾回收器的运行过程大致可划分为以下四个步骤:

初始标记(initial mark),标记了从GC Root开始直接关联可达的对象。STW(Stop the World)执行。
并发标记(concurrent marking),和用户线程并发执行,从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象
最终标记(Remark),STW,标记再并发标记过程中产生的垃圾。
筛选回收(Live Data Counting And Evacuation),制定回收计划,选择多个Region 构成回收集,把回收集中Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。需要STW。

G1的垃圾回收流程主要是从新生代回收开始,新生代回收与并发标记再到混合回收,因此G1的垃圾回收也有阶段性的特点。
G1 Yong Collection
当Eden区域装满时会触发新生代的GC,系统会进入“Stop the World”状态,然后将标记存活的对象移动至Survivor区,达到晋升年龄的就晋升到老年代区域,然后清空原区域(不过这里可没有年轻代复制算法中两个Survivor的交换过程)。
此时G1 会选择把所有的年轻代区域加入回收集合中,但是为了满足用户停顿时间的配置,在每次GC后会调整这个最大年轻代区域的数量,每次回收的区域数量可能是变化的。

G1 Yong Collection + Concunrrent Mark
当G1新生代垃圾回收结束后,紧接着开始进入并发标记阶段:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。

G1 Mixed Collection
当老年代的大小占据了堆内存的45%的Region时(-XX:InitiatingHeapOccupancyPercent的默认值是45%),此时就会触发一个新生代和老年代的混合回收阶段,对E S 0 H区进行全面回收。

Full GC
当在进行混合回收的过程中,由于无论是新生代还是老年代都是基于复制算法进行的,都需要将各个Region中的存活对象拷贝到别的Region中,此时如果一旦出现拷贝的过程中发现没有空闲的Region可以进行存储了,就会触发一次失败!那么这个时候系统会立马切换为我们的Seiral收集器进行单线程的标记、清理和压缩整理,该过程就变得非常的慢了!