线上有一个 HTTP 服务,之前都运行的好好的,突然有一天,出现了一个问题。应用刚刚重启的时候,系统 的 CPU 负载非常高,24 核的 CPU,Load 要去到 40 到 50。持续 2 分钟左右,负载才会慢慢降回正常水平。
项目使用的 web 框架基于 netty,处理请求的方式是队列加线程池。在刚启动的时候,可以看到马上就有 1w 多的连接挂了上来。请求队列中,有上 w 个积压的请求。我们当时的推论是,积压的请求过多,导致负载过高。
这个问题,困扰了我一年多时间,把所有启动相关的代码都 review 了一遍,期间还发现了数据库连接池的问题, 但都没找到问题的根本原因。
直到有一天,另外一个用同样框架的项目也遇到了这个问题,且对他们的影响很大,他们不得不停下手头的工作, 集中精力排查这个问题。最后在 JVM 启动参数中,加上了 -XX:CICompilerCount=12,终于解决了这个问题! 启动后 2-3 秒,负载恢复正常。
这个问题,居然真的跟 JIT 有关!为什么说是真的呢?因为我们也曾怀疑过,刚启动的时候,需要 JIT 来预热。 我们给框架加了一个预热线程池,在刚启动的时候,使用一个非常小的线程池,来处理请求。避免 JVM 中,有过 多的线程抢占 CPU 时间,线程越多,处理越慢。过了十几秒(拍脑袋决定的,估计十几秒后,JIT 能编译大部分 “热”代码),才换一个较大的线程池,来处理请求队列中的请求。这个优化做完后,高负载的持续时间,减少到了 1 分半左右,依然没有解决问题。
从上面的拍脑袋可以看出,当时我们对 JIT 是一知半解,白白浪费了解决问题的灵感。于是昨晚认真看了 《JAVA性能权威指南》 中的 JIT 相关章节,就当作是亡羊补牢吧。