现象
线上机器频繁出现cpu.idle报警,机器load值高
问题排查
- top命令查找到CPU占用率比较高的进程pid
- top -Hp 456 查看Java进程中排序后占用CPU比较高的线程
- 线程ID576转换成16进制,命令:printf %x 576
- jstack 456 | grep "240" 或者 jstack 456 | less然后搜索240线程
如果觉得上面的操作比较麻烦也可以使用开源的脚本直接查看占用CPU比较高的线程的堆栈信息,地址:github.com/oldratlee/u…
问题分析
通过线程堆栈信息可以看到占用CPU比较高的是Java的编译线程。编译线程主要是在运行时将字节码编译成本地代码。HotSpot VM 是采用解释执行 + JIT编译器编译热点代码的方式运行。
编译模式
HotSpot中内置了两个即时编译器,分别称为 Client Compiler和 Server Compiler ,或者简称为 C1 编译器和 C2 编译器。
Client模式
- 采用C1编译器
- 相对C2来说比较轻量,编译前期需要收集的信息也相对较少,编译速度快,但是编译后的代码执行效率没有C2编译后的高。
Server模式
- 采用C2编译器
- 编译前需要收集较多的统计信息,在编译的同时做优化,速度慢,编译后代码执行效率高。
Tiered模式(分层编译模式)
- 分层编译方式是一种折衷方式,在系统启动之初执行频率比较高的代码将先被C1编译器编译,以便尽快进入编译执行。随着时间推进,一些执行频率高的代码会被C2编译器再次编译,从而达到更高的性能。
- 可以设置参数-XX:+TieredCompilation来启动Tiered模式,java 8默认就是Tiered模式。
Code Cache
- 程序启动时代码执行模式为解释执行模式,运行一段时间后根据代码方法执行的次数,或代码里循环的执行次数等达到一定的阈值后会编译成机器码,编译后的机器码被缓存在内存中,具体的内存区域就是code cache。
- code cache空间不足最早编译的一半方法会被回收,如果回收后空间还是不足,JIT会停止编译,后续的代码都使用解释执行的方式,系统整体性能会下降。
- code cache空间回收可能会导致编译线程大量占用CPU。
通过查阅资料分析怀疑本次CPU利用率高和code cache的使用有关系,查看Cat代码缓存区域使用情况
jvm参数codecache设置的是64M,-XX:ReservedCodeCacheSize=64m -XX:InitialCodeCacheSize=64m 通过监控可看到该内存区域基本被占满
解决问题
调整代码缓存区域大小设置最大code cache和初始化code cache大小为128m
-XX:ReservedCodeCacheSize=128m -XX:InitialCodeCacheSize=128m
codecache的设置原则
不宜过小,过小会cpubusy过高拖慢服务响应时间,也不宜过大,过大会导致空间浪费,其他jvm分区空间变小,加大gc时间,一个合理的值是当前使用量的2倍
在JDK 8中,提供了一个启动参数
-XX:+PrintCodeCache在JVM停止的时候打印出codeCache的使用情况。 其中max_used就是在整个运行过程中codeCache的最大使用量。可以通过这个值来设置一个合理的codeCache大小,在保证应用正常运行的情况下减少内存使用。
调整结果
-
1天对比图
-
3天对比图
-
code cache使用情况