花钱的年华

江南白衣,公众号:春天的旁边

快速,低成本,低扰动地运行一段Java代码

| Filed under 技术

JVM是个运行服务端应用的好VM,但如果你只是想频繁地运行一段Java写的脚本,或者在跑一些辅助性的Java程序比如监控,比如日志收集,这时候的诉求就和平日里的应用不一样了:

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

1. 从失败的取经开始

第一时间,看看jmap,jstack们用了什么参数,结果发现通通只有一个-Xms8m (在它们运行时,跑jps -v 可见,源码级确认JDK7见Makefile.launcher,JDK8见CompileLaunchers.gmk)。

另外,传说中的-client,在多核的Linux服务器上的也是无效的。

下面开始自己的折腾,首先给跑的脚本配上"-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime", 在gc.log 里就会有实际启动参数,GC日志,以及结束时打印内存各代的占用。

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

最后,jstack一下进程,看看里面跑着多少线程,一个默认的JVM,线程数多得吓人。

 

2. 类的加载和编译

2.1 -Xverify:none

来自优化Eclipse启动速度的经验,说关闭Java类加载验证可以加快10% -15%的启动速度。

2.2 设定编译级别

JIT编译之后的代码比解释执行字节码更快,更省CPU,但编译本身就需要CPU,也需要额外的编译线程。临时给启动脚本配上 “-XX:+PrintCompilation”,观察编译情况。

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

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

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

2.3 编译线程的设定

在24核服务器上,默认有4条C1编译线程,8条C2编译线程(多层编译下),考虑到低成本原则,建议把它设到最小的-XX:CICompilerCount=2。

2.4 未来黑科技-AOT

JIT不够酷,预先把代码编译(Ahead-of-Time,AOT) 更好。 JDK9里有一个Hotspot编译器组搞的试验性的jaotc,另一个选择是GraalVM全家桶里带的SubstrateVM,这个支持JDK8。两者都是基于Graal,看各位大大炫,但我还没玩过。

 

3. GC 设置

这些脚本,辅助进程一般不介意GC延时,建议使用吞度量最的串行收集算法 -XX:+UseSerialGC,大大减少了其他GC算法所产生的大量GC线程,保证自己GC时也不会影响到主应用。

如果依然想使用并行算法,就一定要设置GC线程数,如果按默认值,在24核机器上YGC和CMS GC的线程数分别是18和5,一旦发生GC,将占用大量的CPU核,直接对主应用产生巨大的影响。可设为 -XX :P arallelGCThreads=2 -XX:ConcGCThreads=1

 

4. 内存设置

JVM的堆内存扩张并没有想象中那么智能,当-Xms 与 -Xmx 不等,又没有指定新生代比例时,新生代大小更是混乱。建议根据运行后的实际占用及GC日志,完整设置一个最合适的值。

快速运行一次的脚本,老生代几乎没用,新生代可以设大些。

-Xms96m -Xmx96m -Xmn64m

长期运行的,则一般设为1:1。

-Xms256m -Xmx256m -XX:NewRatio=1

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

永久代也可以一设,可惜JDK7/8的参数不兼容,用java -version 拿版本号太贵了,所以如果明确了JDK版本的话可以设置,否则就算了。

 

5. 其他

这些脚本,免不了和数字打交道,如果不保证没有int ->Integer的autoboxing,就还是打开-XX:AutoBoxCacheMax=20000

如果有其他更多黑科技,请大家补充。

6. 小结

谢谢你看到这里,这篇的题材比较偏门,幽僻处可有人行。

一段快速执行,关闭了JIT的脚本,启动命令大概长这个样子:

-Xms96m -Xmx96m -Xmn64m -Xss256k -XX:+UseSerialGC -Djava.compiler=NONE -Xverify:none -XX:AutoBoxCacheMax=20000

一段辅助程序,启动命令大概长这个样子

-Xms256m -Xmx256m -XX:NewRatio=1 -Xss256k -XX:+UseSerialGC -XX:-TieredCompilation -XX:CICompilerCount=2 -Xverify:none -XX:AutoBoxCacheMax=20000

VJTools几个工具的启动脚本,都按这个标准重写了一下。

有关的...

发表评论

您的电子邮箱不会被公开。

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>