78

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

 5 years ago
source link: http://calvin1978.blogcn.com/articles/java-low-option.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 YRF7BvM.gif 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 几个工具的启动脚本,都按这个标准重写了一下。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK