1. 恶邻A君 唯品会在服务化体系改造的神秘时原初期,一个对延时敏感的发服应用,偶然会发生一些超时,坏邻事发当时zabbix分钟级监控,神秘时原dstat秒级监控的发服服务器指标都正常,应用,坏邻数据库,神秘时原缓存,发服网络也正常,坏邻那这是神秘时原为什么呢? 某天脑洞大开,把怀疑的发服目光投向了在后台运行日志收集程序Flume,发现它的坏邻GC运行得比较狂野,于是神秘时原对它的GC线程数做了限制: 2. 恶邻B君 又过了若干个月,又有某些应用,又开始抽风。这次相对好查一些,因为我们新升级了服务器的监控系统,源码库只要在两台机器上做一下对比测试就好了。 只花了一个晚上,基本就能验明凶手了。 那这个新升级的监控系统,又是怎么影响到主应用的呢?找出它与应用有交互的部分,原来对于JVM的各种线程数信息,堆内存各代的信息,每拿一个数据都会启动一次JMX Client,所以每分钟都有一秒要连拿7个数据,启动7个JMX Client。 改进方法很简单,我们自己定制了一下JMX Client,将7个数据合并在一个命令里获得,另外定制了一下JMX Client的JVM参数,将它启动的动静尽量减少。 3. 逆优化 可见,JVM是个运行服务端应用的好VM,但体量有点大。如果你只是想频繁地运行一段Java写的脚本,或者在跑一些辅助性的程序比如监控和日志收集,亿华云往常推荐的JVM参数也就不再合适里,需要进行逆优化才能做个安静的好邻居: 4. 从失败的取经开始 ***时间,觉得和JDK自带的jmap,jstack们用一样的参数就好了,多简单。 在它们运行时,跑jps -v ,结果发现通通只有一个-Xms8m 。 还不死心,又去翻源码,JDK7在 Makefile.launcher,JDK8在CompileLaunchers.gmk,结果发现全部8M,通通8M,再没别的参数了。 有同学又从久远的记忆中想起一个-client,感觉也是比较弱气的高防服务器选项,但在这个多核的64位Linux服务器上是根本无效的,一定是-server,必须是-server。 5. 逆优化的思路 JVM与上述诉求相冲的几个地方: 在开始折腾前,先准备好测试手段: 首先,给工具脚本配上GC 日志参数,在GC日志里就能看到实际启动参数,GC纪录,以及运行结束时内存各代的占用。 -Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime 其次,长期跑一个 pidstat -l 1| grep xxx ,紧密监控进程的CPU消耗。 ***,jstack看线程。 6. 类的加载和编译优化 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。 看各位大大炫,但我还没玩过。 7. GC 优化 脚本们一般不介意GC延时,建议使用吞度量最的串行收集算法 -XX:+UseSerialGC,避免了其他GC算法所需的大量GC线程,更绝对保证了自己GC时不会影响到主应用。 如果依然想使用并行算法,就一定要设置GC线程数,在24核机器上YGC和CMS GC的线程数默认分别是18和5,为了避免成为恶邻A君。可设为: -XX:ParallelGCThreads=4 -XX:ConcGCThreads=2 8. 内存优化 建议根据GC日志的结果,完整设置-Xms 和 -Xmx,并用-Xmn(新生代占大头) 或-XX:NewRatio=1(一半半) 来设置新生代大小。 其次,每条线程的内存,从默认1M回到256k: -xss256k 其他***代,CodeCache的初始值还算合理,没看到特别浪费的情况不用管。