You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java JDK17 ZGC环境下JIT反优化引发请求延迟问题咨询

JDK17+ZGC下JIT反优化导致请求变慢的问题分析

问题背景

在JDK17环境中使用ZGC时,出现部分请求执行耗时过长的情况。查看GC日志发现,jit,compilation操作的线程ID对应用户线程(4236),且执行时间与慢请求发生时间完全吻合。需要解答以下问题:

  • 为何jit,compilation会在用户线程中执行?
  • 它为何会阻塞用户线程?
  • JIT反优化(日志中made not entrant)是否消耗大量时间?期间具体执行了哪些操作?
  • 如何避免该问题?

相关GC日志

注:579为Java-Compiler线程,4236为用户线程

[2024-03-27T10:26:37.675+0800][579 ][jit,compilation] 27257       3       java.net.InetAddress::getAllByName (388 bytes)   made not entrant
[2024-03-27T10:26:38.572+0800][579 ][jit,compilation] 46702       4       Script_1711505411710_68000::execute0 (112 bytes)
[2024-03-27T10:26:38.576+0800][579 ][jit,compilation] 39531       3       Script_1711505411710_68000::execute0 (112 bytes)   made not entrant
[2024-03-27T10:26:38.716+0800][4236][jit,compilation] 42949   !   4       com.google.common.cache.LocalCache$Segment::get (122 bytes)   made not entrant
[2024-03-27T10:26:38.716+0800][4236][jit,compilation] 35244       4       com.google.common.cache.LocalCache::isExpired (57 bytes)   made not entrant
[2024-03-27T10:26:38.716+0800][4236][jit,compilation] 28530       4       com.google.common.cache.LocalCache$Segment::recordRead (29 bytes)   made not entrant

问题解答

1. 为何jit,compilation会在用户线程中执行?

常规JIT编译由专门的Java Compiler后台线程(如日志中的579)异步完成,但JIT反优化(deoptimization)操作必须在触发它的用户线程中同步执行。当JVM检测到已编译的优化代码存在错误(比如类型推测失效、方法被动态覆盖、缓存逻辑变更导致代码假设不成立),需要撤销优化并回退到解释执行或重新编译时,这个过程涉及当前用户线程的栈帧调整、执行状态恢复,无法由后台线程代劳,只能在当前执行该代码的用户线程中完成。

2. 为何反优化会阻塞用户线程?

反优化是同步阻塞操作:

  • 用户线程在执行业务代码时触发反优化后,必须暂停当前业务逻辑,优先完成反优化流程。
  • 流程中需要调整调用栈帧(将编译代码的栈帧转换为解释执行格式)、标记方法为not entrant(禁止新线程进入该编译代码),如果有其他线程正在执行该已编译方法,还需等待这些线程退出。
  • 只有等所有反优化操作完成,用户线程才能继续执行业务代码,因此直接导致请求耗时变长。

3. JIT反优化的耗时与具体操作

耗时情况

反优化的耗时取决于三个核心因素:

  • 被反优化方法的调用深度:调用栈越深,需要调整的栈帧越多,耗时越长。
  • 单次触发的反优化方法数量:如日志中同时处理LocalCache相关的多个方法,耗时会累加。
  • 并发执行该方法的线程数:如果有其他线程正在执行该编译代码,需要等待这些线程退出,会额外增加阻塞时间。

单次反优化通常耗时几毫秒到几十毫秒,但如果频繁触发,会显著拉低请求的平均响应时间。

具体操作

当日志中出现made not entrant标记时,JVM会执行以下步骤:

  • 标记方法状态:将该编译后的方法标记为not entrant,禁止新线程进入该编译代码执行。
  • 栈帧转换:扫描当前用户线程的调用栈,找到所有调用该方法的栈帧,将其从编译代码的栈帧格式转换为解释执行的栈帧格式,或替换为未优化的方法版本。
  • 清理缓存:清除该方法关联的JIT编译缓存、类型推测结果等,避免后续复用错误的优化代码。
  • 触发重新编译(可选):如果条件允许,JVM会通知后台编译线程重新编译该方法的正确版本。

4. 如何避免或缓解该问题

  • 减少动态代码变更:避免运行时动态修改类、生成新类(如频繁热部署、动态代理),这类操作极易触发JIT反优化。
  • 调整JIT编译参数:
    • 确保-XX:+TieredCompilation开启(JDK17默认开启),合理调整编译层级,减少层级切换引发的反优化;
    • 调高-XX:CompileThreshold参数,提高方法触发JIT编译的调用次数阈值,避免频繁编译-反编译;
    • 添加-XX:+PrintDeoptimizationDetails参数,打印详细反优化日志,精准定位触发反优化的根源。
  • 优化业务代码:
    • 热点方法中避免使用易触发类型推测失败的逻辑(如频繁类型转换、泛型擦除后的类型判断);
    • 针对日志中涉及的Guava Cache,减少修改缓存过期策略、频繁移除缓存条目等操作,或改用更稳定的缓存实现。
  • 提前预热核心代码:系统启动阶段,对核心业务方法进行预热调用,触发JIT编译并稳定代码状态,避免运行期因首次编译或反优化影响请求。

内容的提问来源于stack exchange,提问作者yiming xie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 21:59:53