神秘的偶发服务超时,原因可能是那些坏邻居

阿里云幸运券

  1. 恶邻A君
    唯品会在服务化体系改造的初期,一个对延时敏感的应用,偶然会发生一些超时,事发当时zabbix分钟级监控,dstat秒级监控的服务器指标都正常,应用,数据库,缓存,网络也正常,那这是为什么呢?

某天脑洞大开,把怀疑的目光投向了在后台运行日志收集程序Flume,发现它的GC运行得比较狂野,于是对它的GC线程数做了限制:

修改前:15分钟内, 大于30ms的业务调用173次, 大于50ms的23次
修改后: 246分钟内,大于30ms的业务调用41次, 大于50ms的4次

  1. 恶邻B君
    又过了若干个月,又有某些应用,又开始抽风。这次相对好查一些,因为我们新升级了服务器的监控系统,只要在两台机器上做一下对比测试就好了。 只花了一个晚上,基本就能验明凶手了。

那这个新升级的监控系统,又是怎么影响到主应用的呢?找出它与应用有交互的部分,原来对于JVM的各种线程数信息,堆内存各代的信息,每拿一个数据都会启动一次JMX Client,所以每分钟都有一秒要连拿7个数据,启动7个JMX Client。

改进方法很简单,我们自己定制了一下JMX Client,将7个数据合并在一个命令里获得,另外定制了一下JMX Client的JVM参数,将它启动的动静尽量减少。

  1. 逆优化
    可见,JVM是个运行服务端应用的好VM,但体量有点大。如果你只是想频繁地运行一段Java写的脚本,或者在跑一些辅助性的程序比如监控和日志收集,往常推荐的JVM参数也就不再合适里,需要进行逆优化才能做个安静的好邻居:

一、启动快速,动静小。
二、低成本,节约CPU、内存和线程。
三、低扰动,不干扰主应用的运行。

  1. 从失败的取经开始
    第一时间,觉得和JDK自带的jmap,jstack们用一样的参数就好了,多简单。

在它们运行时,跑jps -v ,结果发现通通只有一个-Xms8m 。

还不死心,又去翻源码,JDK7在 Makefile.launcher,JDK8在CompileLaunchers.gmk,结果发现全部8M,通通8M,再没别的参数了。

有同学又从久远的记忆中想起一个-client,感觉也是比较弱气的选项,但在这个多核的64位Linux服务器上是根本无效的,一定是-server,必须是-server。

  1. 逆优化的思路
    JVM与上述诉求相冲的几个地方:

各种吃内存

各种后台线程

JIT时CPU表现狂野

GC时CPU表现狂野

那我们就从这几个方面着手。

在开始折腾前,先准备好测试手段:

首先,给工具脚本配上GC 日志参数,在GC日志里就能看到实际启动参数,GC纪录,以及运行结束时内存各代的占用。

-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime

其次,长期跑一个 pidstat -l 1| grep xxx ,紧密监控进程的CPU消耗。

最后,jstack看线程。

  1. 类的加载和编译优化
    6.1 -Xverify:none
    来自优化Eclipse启动速度的经验,说关闭Java类加载验证可以加快10% -15%的启动速度,嗯,好,加。

6.2 设定编译级别
JIT编译之后的代码比解释执行字节码更快,更省CPU。比如vjtools里的vjtop,没编译时每回运行要50%单核CPU,75ms执行完一个探测循环,而编译后10%单核,15ms就能完工。

但编译本身就需要CPU,也需要额外的编译线程。

如果脚本只简单的跑一次,比如vjtools里的vjmxcli,建议就不要进行JIT编译了,编译完了也用不上,直接解释执行就好。禁止它:-Djava.compiler=NONE

如果脚本用于密集计算,比如vjtools里的vjmap,则建议打开多层编译,一开始就对运行到的方法进行静态编译,不用等方法被调用1万次。多层编译在JDK8默认打开,显式打开:-XX:+TieredCompilation。

但打开多层编译也会导致程序运行初期有较多的编译任务,吃比较多的CPU,可以显式关掉多层编译 -XX:-TieredCompilation来对比一下,综合其带来的性能提升,脚本的运行时间的长短,以及额外的CPU支出来综合评价。

6.3 编译线程的设定
在24核服务器上,默认有4条C1编译线程,8条C2编译线程(多层编译下),可以把它设到最小的-XX:CICompilerCount=2。

6.4 未来黑科技-AOT
JIT真的不适合脚本,还是预先把代码编译(Ahead-of-Time,AOT) 更好。 JDK9里有一个Hotspot编译器组搞的试验性的jaotc,另一个选择是GraalVM全家桶里带的SubstrateVM,支持JDK8。

看各位大大炫,但我还没玩过。

  1. GC 优化
    脚本们一般不介意GC延时,建议使用吞度量最的串行收集算法 -XX:+UseSerialGC,避免了其他GC算法所需的大量GC线程,更绝对保证了自己GC时不会影响到主应用。

如果依然想使用并行算法,就一定要设置GC线程数,在24核机器上YGC和CMS GC的线程数默认分别是18和5,为了避免成为恶邻A君。可设为:

-XX:ParallelGCThreads=4 -XX:ConcGCThreads=2

  1. 内存优化
    首先,JVM的堆内存

一,默认的JVM初始内存大小,在大内存的服务器上会比较大,必须设置。

二,-Xms 与 -Xmx 不等时, 自动扩张并没有想象中那么智能和合理。

三、新生代默认只有1/3堆大小,而在脚本看来新生代才是大头。

建议根据GC日志的结果,完整设置-Xms 和 -Xmx,并用-Xmn(新生代占大头) 或-XX:NewRatio=1(一半半) 来设置新生代大小。

其次,每条线程的内存,从默认1M回到256k: -xss256k

其他永久代,CodeCache的初始值还算合理,没看到特别浪费的情况不用管。

  1. 小结
    想起写这个,主要是看杨晓峰的《Java核心技术36讲》里的某几篇挑起的兴趣。

腾讯云代金券

原文链接

https://mp.weixin.qq.com/s?__biz=MzIzODYyNjkzNw%3D%3D&mid=2247483926&idx=1&sn=762a1fa842091332d4ab9bae115c83d7&chksm=e9373300de40ba16fcd921709c8c64feafd9e8b4dac48e59a155ce62e72e7c09d6bafc3f84c1&mpshare=1&scene=23&srcid=0810CdFf36tzlTEJT9p3xkHT%23rd

服务推荐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值