研究背景
通常情况下应用发布或重启时都存在cpu抖动飙高,甚至打满的现象,这是由于应用启动时,JVM重新进行类加载与对象的初始化,CPU在整个过程中需要进行比平时更多的编译工作。同样,闲鱼的消息系统在重新发布时经常有抖动的问题,如下图显示:日常情况下CPU使用率基本不超过20%,而每当应用重新发布时,服务器的cpu使用率骤增至40%以上。本文正是为了减少这种抖动,进而保障应用发布时的稳定性。
Java的编译
发布时CPU利用率的飙高很大程度上是编译造成的,因此在处理问题之前,我们需要了解Java编译的机制,这对于后续的理解很重要。如果已经该部分知识非常熟悉,则可以跳过本节直接阅读第三部分。常见的编译型语言如C++,通常会把代码直接编译成CPU所能理解的机器码来运行。然而为了实现“一次编译,处处运行”的特性。
Java把编译的过程分成两个阶段:
• 先由javac编译成通用的中间形式(字节码),该阶段通常被称为编译期。
• 解释器逐条将字节码解释为机器码来执行,该阶段则属于运行期。
为了优化Java字节码运行的性能 ,HotSpot在解释器之外引入了JIT(Just In Time)即时编译器,形成了用解释器+JIT编译器混合的执行引擎
二者会在运行期并肩作战,但分工不同:
• 解释器(Interpreter):当程序需要迅速启动时,使用解释器解释字节码,节省编译的时间,快速执行。
• JIT编译器(JIT Compiler):在程序启动后并且长时间提供服务时,JIT将越来越多的代码编译为本地机器码,获得更高的执行效率。
Java程序在JVM上执行的过程如下图所示:
编译期先由javac将源码编译成字节码,在这个过程中会进行词法分析、语法分析、语义分析等操作,该过程也被称为前端编译。当类加载完成,程序运行时,JVM会利用热点代码计数器进行判断,如果此时运行的代码是热点代码则使用JIT,如果不是则使用解释器。对于热点代码的判断方式有采样估计和计数两种方式,Hotspot采用计数方式,到达一定阈值时触发编译。
大多数情况下解释器首先发挥作用,将字节码按条解释执行。随着时间推移,通过不断对解释的代码进行信息采集,JIT逐渐发挥作用。把越来越多的字节码编译优化为本地机器码并存储在CodeCache中,来获取更高的执行效率。解释器这时可以作为编译运行的降级手段,在一些不可靠的编译优化出现问题时,再切换回解释执行,保证程序可以正常运行。JIT极大地提高了Java程序的运行速度,而且跟静态编译相比,即时编译器可以选择性地编译热点代码,省去了很多编译时间,也节省很多的空间。
定位问题
在线诊断
Arthas 是阿里巴巴推出的一款免费的线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息。首先,我们使用Arthas在预发环境下连接服务器,进而对对常规时刻的CPU使用率进行监控,操作步骤如下:
下载:>> curl -O https://arthas.aliyun.com/arthas-boot.jar
启动:>> java -jar arthas-boot.jar
看板:>> dashboard
dashboard面板显示如下图,可以看到此时的各线程所占用的CPU。随后,我们进行应用的发布重启,来观察该过程的CPU使用率变化,下图是常规时段的CPU利用率。
随后我们开始发布应用。开始发布后不久搭建的ssh链接会被服务器断开,此时应用被停止。在连接断开之前捕捉到了如下记录,“VM Thread”等线程,占用了一定的的CPU,但不是很多。