文章目录
前言
我们可以使用JITWatch来观测线上代码实际运行逻辑,来进行一些问题分析和学习参考。虽然线上运行的是机器指令,但是为了方便阅读和理解,JITWatch执行的都是汇编代码,是ARM指令集的汇编代码,不过咱们可以借助chat-gpt或者豆包等大模型帮忙解读
一、JITWatch是什么?
JITWatch是一个反编译查看工具,顾名思义,可以用来Watch JIT代码,帮助我们分析代码实际运行过程中的逻辑。
二、问题描述
1.公司线上问题
某个团队发现线上读TP999,总会时不时地出现一次抖动,通过公司的技术大盘观测,同一时间cpu.busy也会突然抖动一下。然后他们结合JFR,在线上服务再次复现问题时,观测到活跃线程居然是“C2 Compiler Thread”,咱们javaer看到这个,都会有点眼熟吧,毕竟各种八股文都背过。进一步观测发现,此时C2 Compiler Thread都是在进行分支预测(profile_predicate)失败导致的“逆优化,再编译”。
1.1 分支预测
其实这个问题,我也是学习到了,本来以为分支预测只会发生在操作系统调度指令流水线期间,如果预测失败,会清空指令流水线,重新加载另外一个分支。没想到C2编译器啊,你这个浓眉大眼的,居然在编译层面就做了一些分支预测的工作。
1.2 逆优化,再编译
咱们都知道C2的作用,就是为了做JIT,将字节码编译为机器码,然后运行。那逆优化,再编译,其实就是指编译的机器码出了问题,需要退化为字节码,再进行解释执行。
本来是操作系统可直接执行的机器指令,逆优化变为了Java的字节码指令,jvm的解释器需要借助CPU再次解释字节码指令为机器指令,然后CPU重新执行机器指令。也就是指令的生命周期那套。
2. 模拟问题(复现)
2.1 模拟环境
系统:macOS
Java版本:Java17
HotSpot17:直接下载就行
openjdk17:因为必须要修改源码,所以使用openjdk来做
JITWatch
:jitwatch-ui-1.4.7-shaded-mac.jar
下载地址:https://github.com/AdoptOpenJDK/jitwatch/releases
2.2 准备工作
- 下载openjdk17:https://github.com/openjdk/jdk17u
- 修改分支预测代码逻辑:src/hotspot/share/opto/parse2.cpp:1291,将branch_prediction改为以下代码
//-----------------------------branch_prediction-------------------------------
float Parse::branch_prediction(float& cnt,
BoolTest::mask btest,
int target_bci,
Node* test) {
return PROB_FAIR;
}
- 编译openjdk17
编译好的jdk的java_home:jdk17u/build/macosx-x86_64-server-slowdebug/images/jdk - 下载JITWatch
2.3 复现步骤
- Java代码,咱们这段代码的某段逻辑一定不会走进去,也就是return 0x666
public class FirstTest {
private static int test(int num) {
if (num > 0) {
return 0x123;
}
return 0x666;
}
public static void main(String[] args) {
long res = 0;
for (int j = 0; j < 100_000_000_0L; j++) {
res += test(j);