CPU利用率高问题排查

714 阅读3分钟

现象

线上机器频繁出现cpu.idle报警,机器load值高 image.png

image.png

问题排查

  • top命令查找到CPU占用率比较高的进程pid

image.png

  • top -Hp 456 查看Java进程中排序后占用CPU比较高的线程

image.png

  • 线程ID576转换成16进制,命令:printf %x 576

image.png

  • jstack 456 | grep "240" 或者 jstack 456 | less然后搜索240线程

image.png

如果觉得上面的操作比较麻烦也可以使用开源的脚本直接查看占用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代码缓存区域使用情况

image.png

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天对比图 image.png

  • 3天对比图 image.png

  • code cache使用情况 image.png

参考资料

blog.csdn.net/yandaonan/a…

blog.andresteingress.com/2016/10/19/…

docs.oracle.com/javase/8/em…