龙空技术网

JVM 参数调优

云端行笔 98

前言:

眼前兄弟们对“k空间数据哪里有”大致比较着重,大家都想要学习一些“k空间数据哪里有”的相关资讯。那么小编也在网上收集了一些有关“k空间数据哪里有””的相关内容,希望各位老铁们能喜欢,同学们快快来了解一下吧!

时至今日,生产环境中依旧是JDK8占据主流,因此,本文将围绕JDK8常用的CMS和G1进行相关参数的讲解。

启动参数分类

  标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容。例如:-verbose:class(输出jvm载入类的相关信息,当jvm报告说找不到类或者类冲突时可此进行诊断);-verbose:gc(输出每次GC的相关情况);-verbose:jni(输出native方法调用的相关情况,一般用于诊断jni调用错误信息)。

java -help:: 展示所有的 JVM 标准参数

  非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容。例如:-Xms512m;-Xmx512m;-Xmn200m;-Xss128k;-Xloggc:file(与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中,文件的位置最好在本地,以避免网络的潜在问题。若与verbose命令同时出现在命令行中,则以-Xloggc为准)。

java -X:展示所有的 JVM 非标准参数

  非Stable参数(-XX),此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用。例如:-XX:PermSize=64m;-XX:MaxPermSize=512m。

java -XX:+PrintFlagsFinal:展示所有不稳定参数

具体参数配置说明可参考:JVM启动参数有哪些_Serverless 应用引擎-阿里云帮助中心

CMS

CMS 是一种基于并发、使用标记清除算法的垃圾回收器。CMS 会尽可能让 GC 线程与用户线程并发执行,可以消除长时间的 GC 停顿(STW)。

CMS 不会对新生代做垃圾回收,默认只针对老年代进行垃圾回收。此外,CMS 还可以开启对永久代的垃圾回收(或元空间),避免由于 PermGen 空间耗尽带来 Full GC,JDK6以上受参数 -XX:+CMSClassUnloadingEnabled 控制,这个参数在 JDK8 之前默认关闭,JDK8 默认开启了。

CMS 要与一个新生代垃圾回收器搭配使用,所谓"分代收集"。能与 CMS 配合工作的新生代回收器有 Serial 收集器和 ParNew 收集器,我们一般使用支持多线程执行的 ParNew 收集器。

使用 CMS GC 策略时,GC 类别可以分为:Young GC(又称 Minor GC),Old GC(又称 Major GC、CMS GC),以及Full GC。其中 Full GC 是对整个堆的垃圾回收,STW 时间较长,对业务影响较大,应该尽量避免 Full GC。

如下参数配置可做参考:

-Xms4g -Xmx4g -Xmn1g -Xss256k -XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256m-XX:+UseConcMarkSweepGC -XX:+UseParNewGC  -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSClassUnloadingEnabled -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses -verbose:gc-XX:+PrintGCDetails-XX:+PrintGCDateStamps-Xloggc:/data/log/gclog/gc.log-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/log/jvmdump/ 
垃圾回收器:CMS+parNew
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC

年轻代采用ParNew,老年代采用CMS(concurrent mark-sweep)

堆大小 -Xmx, -Xms, -Xmn

-Xmx、-Xms 分别表示 JVM 堆的最大值,初始化大小。-Xmx 等价于-XX:MaxHeapSize,-Xms 等价于-XX:InitialHeapSize。由于采用的CMS+ParNew。建议堆大小不要超过8G,最好6G以内,因为CMS+ParNew组合情况下发生的FGC是采用MSC算法且单线程回收,如果堆内存很大,FGC时STW时间会非常恐怖。

-Xmn表示新生代大小,等价于-XX:MaxNewSize、-XX:NewSize,这个参数的设置对 GC 性能影响较大,设置小了会影响 CMS GC 性能,设置大了会影响 Young GC 性能,建议取值范围在1~3g。

-Xmx4g -Xms4g -Xmn1g
-XX:SurvivorRatio

新生代中 Eden 区与 Survivor 区的比值,默认8,这个参数设置过大会导致 CMS GC 耗时过长。适当调小,使得短寿对象在Young区可以被充分回收,减少晋升到Old区的对象数量,以此提升 CMS GC 性能。

线程栈 -Xss

线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。JDK8默认的线程栈大小为1M,有点偏大建议值256k。

-Xss256k 
元数据空间

如果是微服务架构,那么对于绝大部分应用来说,128M的元数据完全够用。不过,JDK8的元数据空间并不是指定多少就初始化多大的空间。而是按需扩展元数据空间。所以,我们可以设置256M。如果不设置这两个参数的话,元数据空间默认初始化只有20M出头,那么就会在应用启动过程中,Metaspace扩容发生FGC。

-XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m
压缩 -XX:+UseCMSCompactAtFullCollection

CMS GC采用的是标记清除算法,而不是压缩算法。意味着随着时间的推移,碎片会越来越多,JVM终究会触发内存整理这个动作。那么,什么时候整理内存碎片呢?跟下面两个参数有很大的关系。

-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。

-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩。

CMS GC不是FGC,在CMS GC搞不定的时候(比如:concurrent mode failure),会触发完全STW但不压缩内存的FGC,或者触发完全STW并且压缩内存的FGC。所以,这个参数的意思就是,连续多少次不压缩内粗的FGC后触发压缩内存的FGC。如果中间出现了CMS GC,那么又需要重新计数不压缩内存FGC发生的次数。

-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 
-XX:ParallelGCThreads, -XX:ParallelCMSThreads

分别表示 Young GC 与 CMS GC 工作时的并行线程数,建议根据处理器数量进行合理设置。

-XX:+UseCMSInitiatingOccupancyOnly , -XX:CMSInitiatingOccupancyFraction

由于CMS收集器不是独占式的回收器,在CMS回收过程中,应用程序仍然在不停地工作。在应用程序工作过程中,又会不断产生垃圾。这些新垃圾在当前CMS回收过程中是无法清除的。同时,因为应用程序没有中断,所以在CMS回收过程中,还应该确保应用程序有足够的内存可用。因此,CMS回收器不会等待堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到某一阈值时便开始进行回收,以确保应用程序在CMS工作中仍然有足够的空间支持应用程序运行。

-XX:CMSInitiatingOccupancyFraction 表示触发 CMS GC 的老年代使用阈值,一般设置为 70~80(百分比)。如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少年老代回收的次数可以较为明显地改善应用程序性能;反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发年老代串行收集器。默认为 -1,表示 CMS GC 会由 JVM 自动触发。

如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足,此时,CMS回收就会失败,虚拟机将启动SerialOld串行收集器进行垃圾回收。如果这样,应用程序将完全中断,直到垃圾回收完成,这时,应用程序的停顿时间可能会较长。

-XX:+UseCMSInitiatingOccupancyOnly 表示 CMS GC 始终基于 CMSInitiatingOccupancyFraction 触发。如果未设置该参数则 JVM 只会根据 CMSInitiatingOccupancyFraction 触发第一次 CMS GC ,后续还是会自动触发。建议同时设置这两个参数。

-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly 
-XX:MaxTenuringThreshold

对象从新生代晋升到老年代的年龄阈值(每次 Young GC 留下来的对象年龄加一),默认值15,表示对象要经过15次 GC 才能从新生代晋升到老年代。设置太小会严重影响 CMS GC 性能,建议默认值即可。

-XX:+CMSClassUnloadingEnabled

表示开启 CMS 对永久代的垃圾回收(或元空间),避免由于永久代空间耗尽带来 Full GC。

-XX:+ExplicitGCInvokesConcurrent and -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

如今,被广泛接受的最佳实践是避免显式地调用GC(所谓的“系统GC”),即在应用程序中调用system.gc()。然而,这个建议是不管使用的GC算法的,值得一提的是,当使用CMS收集器时,系统GC将是一件很不幸的事,因为它默认会触发一次Full GC。幸运的是,有一种方式可以改变默认设置。

-XX:+ExplicitGCInvokesConcurrent命令JVM无论什么时候调用系统GC,都执行CMS GC,而不是Full GC。

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses保证当有系统GC调用时,永久代也被包括进CMS垃圾回收的范围内。因此,通过使用这些标志,我们可以防止出现意料之外的”stop-the-world”的系统GC。

dump路径

设定如下两个参数(HeapDumpPath参数指定的路径需要提前创建好,JVM没办法在生成dump文件时创建该目录),当JVM内存导致导致JVM进程退出时能自动在该目录下生成dump文件。

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/log/jvmdump/ 
GC日志

和dump路径一样,必须提前创建好,JVM无法自动创建该目录。

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/log/gclog/gc.log 
G1-XX:+UseG1GC: 手动指定使用G1收集器执行内存回收任务。-XX:G1HeapRegionSize: 设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。-XX:MaxGCPauseMillis: 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms。-XX:ParallelGCThread: 设置STW时GC线程数的值。最多设置为8。-XX:ConcGCthreads: 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。-XX:InitiatingHeapOccupancyPercent: 设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。ParallelGCThreads参数

在讲这个参数之前,先谈谈JVM垃圾回收(GC)算法的两个优化标的:吞吐量和停顿时长。JVM会使用特定的GC收集线程,当GC开始的时候,GC线程会和业务线程抢占CPU时间,吞吐量定义为CPU用于业务线程的时间与CPU总消耗时间的比值。为了承接更大的流量,吞吐量越大越好。

为了安全的垃圾回收,在GC或者GC某个阶段,所有业务线程都会被暂停,也就是STW(Stop The World),STW持续时间就是停顿时长,停顿时长影响响应速度,因此越小越好。

这两个优化目标是有冲突的,在一定范围内,参与GC的线程数越多,停顿时长越小,但吞吐量也越小。生产实践中,需要根据业务特点设置一个合理的GC线程数,取得吞吐量和停顿时长的平衡。

PS MarkSweep/PS Scavenge, ConcurrentMarkSweep/ParNew, G1等,都可以通过ParallelGCThreads参数来指定JVM在并行GC时参与垃圾收集的线程数。该值设置过小,GC暂停时间变长影响RT,设置过大则影响吞吐量,从而导致CPU过高。

ParallelGCThreads设置

GC并发线程数可以通过JVM启动参数: -XX:ParallelGCThreads=来指定。在未明确指定的情况下,JVM会根据逻辑核数ncpus,采用以下公式来计算默认值:

当ncpus小于8时,ParallelGCThreads = ncpus

否则 ParallelGCThreads = 8 + (ncpus - 8 ) ( 5/8 )

一般来说,在无特殊要求下,ParallelGCThreads参数使用默认值就可以了。但是在JRE版本1.8.0_131之前,JVM无法感知Docker的CPU限制,会使用宿主机的逻辑核数计算默认值。 比如部署在128核物理机上的容器,JVM中默认ParallelGCThreads为83,远超过了容器的核数。过多的GC线程数抢占了业务线程的CPU时间,加上线程切换的开销,较大的降低了吞吐量。因此JRE 1.8.0_131之前的版本,未明确指定ParallelGCThreads会有较大的风险。

ParallelGCThreads建议

ParallelGCThreads配置存在风险的应用,修改方式为以下两种方案(任选一种):

升级JRE版本到1.8.0_131以上,推荐1.8.0_192

在JVM启动参数明确指定 -XX:ParallelGCThreads=,N为下表的推荐值:

容器核数

2

4

8

16

32

64

推荐值

2

4

8

13

23

43

建议上下界

1~2

2~4

4~8

8~16

16~32

32~64

番外篇 - CMS G1 对比

CMS

优点:

支持并发收集.低停顿,因为CMS可以控制将耗时的两个stop-the-world操作保持与用户线程恰当的时机并发执行,并且能保证在短时间执行完成,这样就达到了近似并发的目的.

缺点:

CMS收集器对CPU资源非常敏感,在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分CPU资源,如果在CPU资源不足的情况下应用会有明显的卡顿。无法处理浮动垃圾:在执行‘并发清理’步骤时,用户线程也会同时产生一部分可回收对象,但是这部分可回收对象只能在下次执行清理时才会被回收。如果在清理过程中预留给用户线程的内存不足就会出现‘Concurrent Mode Failure’,一旦出现此错误时便会切换到SerialOld收集方式。CMS清理后会产生大量的内存碎片,当有不足以提供整块连续的空间给新对象/晋升为老年代对象时又会触发FullGC。且在1.9后将其废除。

使用场景

它关注的是垃圾回收最短的停顿时间(低停顿),在老年代并不频繁GC的场景下,是比较适用的。

G1

优点

像CMS收集器一样,能与应用程序线程并发执行。整理空闲空间更快。需要GC停顿时间更好预测。不希望牺牲大量的吞吐性能。不需要更大的Java Heap。

G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:

G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。G1的 Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。

使用场景(参考官方文档

实时数据占用超过一半的堆空间对象分配或者晋升的速度变化大希望消除长时间的GC停顿(超过0.5-1秒)

标签: #k空间数据哪里有