β

关键系统的 JVM 参数推荐(2018 仲夏版)

Harries Blog™ 88 阅读

年更贴,因为两年里遇到的事情,一些想法变了,也补充了VJTools的内容,比如为伸手党们准备的 j vm-options.sh 。

在关键的业务系统里,除了继续追求技术人员最爱的高吞吐与低延时之外,系统的稳定性与排查问题的便捷性也很重要。这是本文的一个原则,后面也会一次又一次的强调。

前言1,资料

1. 学习 开源项目 的启动脚本是个不错的主意,比如 ElasticSearch家的 , Cassandra 家的 。

2. VJTools的 jvm-options.sh ,伸手党们最爱,根据自己需要稍微 定制 一下就行,详见 http :// git hub.com/v ip shop/vjtools 。

3. 《 JVM 调优的"标准 参数 "的各种陷阱》 ,R大的 文章 ,在JDK6时写的,期待更新。

注:老规矩,所有淡蓝色的字体,点击阅读原文跳转到 博客 里有真正链接。

前言2, -XX:+PrintFlagsFinal打印参数值

当你在网上兴冲冲找到一个可优化的参数时,先打印看看,它可能已经默认打开了,再找到一个,还是默认打开了…

JDK7与JDK8,甚至JDK7中的不同小版本,有些参数值都不一样,所以不要轻信网上任何文章,一切以生产环境同版本的JDK打出来的为准。

经常以类似下面的语句去查看参数,偷懒不起应用,用-version代替。有些参数设置后会影响其他参数,所以也得带上。

java  -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -version| grep GCThreads

对于不同版本里的默认值,建议是顺势而为,JDK在那个版本默认打开不打开总有它的理由。 安全 第一,没有很好的因由,不要随便因为网上某篇文章的推荐(包括你现在在读的这篇)就去设置。

关键系统的 JVM 参数推荐(2018 仲夏版)

1. 性能篇

1.1 建议的性能参数

1. 取消偏向 : -XX:-UseBiasedLocking

JDK1.6开始默认打开的偏向锁,会尝试把锁赋给第一个访问它的 线程 ,取消 同步 块上的 synchronized 原语。如果始终只有一条线程在访问它,就成功略过同步操作以获得性能提升。

但一旦有第二条线程访问这把锁,JVM就要撤销偏向锁恢复之前的状态,如果打开安全点日志,可以看到不少RevokeBiasd的纪录,像GC一样Stop The World的干活,虽然只是很短的停顿,但对于 多线程 并发 的应用,取消掉它反而有性能的提升,所以Cassandra就取消了它。

2. 加大Integer Cache: -XX:AutoBoxCacheMax=20000

Integer i=3; 这语句有着 int自动装箱成Integer的过程,JDK默认只 缓存 -128 ~ +127的Integer 和 Long,超出范围的数字就要即时构建新的Integer对象。设为20000后,我们应用的 QPS 有足足4%的提升。为什么是2万呢,因为 -XX:+AggressiveOpts 里也是这个值。 详见 《Java Integer(-128~127)值的==和 equals 比较产生的思考》。

3. 启动时访问并置零内存页面: -XX:+AlwaysPreTouch

启动时就把参数里说好了的内存全部舔一遍,可能令得启动时慢上一点,但后面访问时会更流畅,比如页面会连续分配,比如不会在晋升老生代时才去访问页面使得GC停顿 时间 加长。ElasticSearch和Cassandra都打开了它。

4. SecureRandom生成加速: -Djava.security.egd=file:/dev/./urandom

此江湖偏方原因是Tom cat 的SecureRandom显式使用SHA1PRNG算法时,初始因子默认从 /dev/random 读取会存在堵塞。额外效果是SecureRandom的默认算法也变成更合适的SHA1了。详见 《SecureRandom的江湖偏方与真实效果》

1.2 可选的性能参数

1. -XX:+PerfDisableSharedMem

Cassandra家的一个参数,一直没留意,直到发生高 IO 时的JVM停顿。原来JVM经常会默默的在 /tmp/hperf 目录 写上一点statistics 数据 ,如果刚好遇到PageCache刷盘,把文件阻塞了,就不能结束这个Stop the World的安全点了。

禁止JVM写statistics数据的代价,是jps和 js tat 用不了,只能用JMX,而JMX取新老生代的使用百分比还真没jstat方便,VJTools的vjmxcli弥补了这一点。详见 《The Four Month Bug: JVM statistics cause garbage collection pauses》

2. -XX:-UseCounterDecay

禁止JIT调用计数器衰减。默认情况下,每次GC时会对调用计数器进行砍半的操作,导致有些方法一直温热,永远都达不到触发C2 编译 的1万次的阀值。

3. -XX:-TieredCompilation

多层编译是JDK8后默认打开的比较骄傲的功能,先以C1静态编译,采样足够后C2编译。

但我们实测,性能最终略降2%,可能是因为有些方法C1编译后C2不再编译了。应用启动时的偶发服务超时也多了,可能是忙于编译。所以我们将它禁止了,但记得打开前面的-XX:-UseCounterDecay,避免有些温热的方法永远都要解释执行。

1.3 不建议的性能参数

1. -XX:+AggressiveOpts

一些还没默认打开的优化参数集合, -XX:AutoBoxCacheMax 是其中的一项。但如前所述,关键系统里不建议打开。虽然通过 -XX:+AggressiveOpts - XX:-AggressiveOpts 的对比,目前才改变了三个参数,但为免以后某个版本的JDK里默默改变更多激进的 配置 ,还是不要打开了。

2. JIT Compile相关的参数,函数调用多少次之后开始编译的阀值,内联函数大小的阀值等等,不要乱改。

3. -server ,在64位多核的 linux 中,你想设成 - client 都不行的,所以写了也是白写。

关键系统的 JVM 参数推荐(2018 仲夏版)

2. 内存与GC篇

2.1 GC策略

为了稳健,还是8G以下的堆还是CMS好了,G1现在虽然是默认了,但其实在小堆里的表现也没有比CMS好,还是JDK11的ZGC引人期待。

1.CMS基本写法

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

因为我们的监控系统会通过JMX监控内存达到90%的状况,所以设置让它75%就开始跑了,早点开始也能减少 Full GC 等意外情况(概念重申,这种主动的CMS GC,和JVM的老生代、永久代、堆外内存完全不能分配内存了而强制Full GC是不同的概念)。

为了让这个设置生效,还要设置 UseCMSInitiatingOccupancyOnly ,否则75%只被用来做开始的参考值,后来还是JVM自己算。

2. -XX:MaxTenuringThreshold=2

这是改动效果最明显的一个参数了。对象在Survivor区最多熬过多少次Young GC后晋升到年老代,JDK8里CMS 默认是6,其他如G1是15。

Young GC是最大的应用停顿来源,而YGC后存活对象的多少又直接影响停顿的时间,所以如果清楚Young GC的执行频率和应用里大部分临时对象的最长 生命 周期,可以把它设的更短一点,让其实不是临时对象的新生代对象赶紧晋升到年老代,别呆着。

-XX:+PrintTenuringDistribution 观察下,如果后面几代的大小总是差不多,证明过了某个年龄后的对象总能晋升到老生代,就可以把晋升阈值设小,比如 JMeter 里2就足够了。

3. -XX:+ExplicitGCInvokesConcurrent 但不要加-XX:+DisableExplicitGC

让full gc时使用CMS算法,不是全程停顿,必选。

但像R大说的,System GC是保护机制(如堆外内存满时清理它的堆内引用对象),禁了system.gc() 未必是好事,只要没用什么特别烂的类库,真有人调了总有调的原因,所以不应该加这个烂大街的参数。

4.-XX:+ParallelRefProcEnabled 和-XX:+CMSParallelInitialMarkEnabled

并行的处理Reference对象,如WeakReference, 默认为false, 除非在GC log里出现Reference处理时间较长的日志,否则效果不会很明显,但我们总是要JVM尽量的并行,所以设了也就设了。同理还有-XX:+CMSParallelInitialMarkEnabled,JDK8已默认开启,但小版本比较低的JDK7甚至不支持。

5. ParGCCardsPerStr id eChunk

Linkined的黑 科技 , 2016版不建议打开,后来发现有些场景的确能减少YGC时间,详见 《难道他们说的都是真的》, 简单说就是影响YGC时扫描老生代的时间,默认值256太小了,但32K也未必对,需要自己试验。

-XX:+UnlockDiagnosticVMOptions -XX:ParGCCardsPerStrideChunk=1024

2.2 可选的GC参数

1. 并发收集线程数

ParallelGCThreads=8+( Processor - 8 ) ( 5/8 ); ConcGCThreads = (ParallelGCThreads + 3)/4

比如双CPU,六核,超线程就是24个 处理器 ,小于8个处理器时ParallelGCThreads按处理器数量,大于时按上述公式YGC线程数=18, CMS GC线程数=5。

CMS GC线程数的公式太怪,也有人提议简单改为YGC线程数的1/2。

一些不在乎停顿时间的后台辅助程序,比如日志收集的logstash,建议把它减少到2,避免在GC时突然占用太多CPU核,影响主应用。

而另一些并不独占 服务器 的应用,比如旁边跑着一堆sidecar的,也建议减少YGC线程数。

一个真实的案例,24核的服务器,默认18条YGC线程,因为旁边有个繁忙的 Service Mesh Proxy 在跑着,这18条线程并不能100%的抢到CPU,出现了不合理的慢GC,把线程数降低到12条反而更快了。 所以那些贪心的把YGC线程数=CPU 核数的,通常弄巧成拙。

2. -XX:-CMSClassUnloadingEnabled

在CMS中清理永久代中的过期的Class而不等到Full GC,JDK7默认关闭而JDK8打开。看自己情况,比如有没有运行动态语言脚本如Groovy产生大量的临时类。它有时会大大增加CMS GC的暂停时间。所以如果新类加载并不频繁,这个参数还是显式关闭的好。

3. -XX:+CMSScavengeBeforeRemark

默认为关闭,在CMS remark前,先执行一次minor GC将新生代清掉,这样从老生代的对象引用到的新生代对象的个数就少了,停止全世界的CMS remark阶段就短一些。但如果打开了,会让一次YGC紧接着一次CMS GC,使得停顿的总时间加长了。

又一个真实案例,CMS GC的时间和新生代的使用量成比例,新生代较小时很快完成,新生代快满时CMS GC的停顿时间超过2秒,这时候就还是打开了划算。

2.3 不建议的GC参数

1. -XX:+UseParNewGC

用了CMS,新生代收集默认就是它,不用自己设。

2. -XX:CMSFullGCsBeforeCompaction

默认为0,即每次Full GC都对老生代进行碎片整理压缩。Full GC 不同于 老生代75%时触发的CMS GC,只在老生代达到100%,堆外内存满,老生代碎片过大无法分配 空间 给新晋升的大对象这些特殊情况里发生,所以设为每次都进行碎片整理是合适的,详见

此贴里R大的解释

3.-XX:+GCLockerInvokesConcurrent

我们犯过的错,不是所有Concurrent字样的参数都是好参数,加上之后,原本遇上JNI GCLocker只需要补偿YGC就够的,变成要执行YGC + CMS GC了。

2.4 内存大小的设置

其实JVM除了显式设置的 -Xmx 堆内存,还有一堆其他占内存的地方(堆外内存,线程栈,永久代,二进制 代码 cache ),在容量规划的时候要留意。

关键业务系统的服务器上内存一般都是够的,所以尽管设得宽松点。

1. -Xmx, -Xms,

作者:Harries Blog™
追心中的海,逐世界的梦
原文地址:关键系统的 JVM 参数推荐(2018 仲夏版), 感谢原作者分享。