bigywater.tech
Java · 203

HotSwap:为什么调试时只能改部分方法体

调试器里改代码为什么有时能热更新、有时必须重启?答案就在 JVM 的类重定义能力里。这篇用 Instrumentation.redefineClasses 把每一种修改试一遍,再看已有实例和正在执行的方法会怎样(Instrumentation 与 JVMTI 的基础见前两篇)。

只能改方法体
热替换成功的唯一一类修改
attempted to add a method
新增方法时的失败原因
旧代码跑完
重定义时正在栈上执行的方法的结果

哪些修改能成功

实测 · 本机执行输出
  只改方法体(calc 改成 a * 100,name 返回 v2-name) → 成功
  新增一个方法                             → UnsupportedOperationException: class redefinition failed: attempted to add a method
  新增一个字段                             → UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
  删除一个方法                             → UnsupportedOperationException: class redefinition failed: attempted to delete a method
  改方法签名(calc(int) → calc(long))      → UnsupportedOperationException: class redefinition failed: attempted to delete a method
  改方法的访问修饰符(public → private)        → UnsupportedOperationException: class redefinition failed: attempted to change method modifiers
  让类实现一个接口                           → UnsupportedOperationException: class redefinition failed: attempted to change superclass or interfaces
  改 static 方法体                       → 成功

已有实例与正在执行的方法

实测 · 本机执行输出
  重定义之前:name() = v1-name,calc(5) = 6,st() = v1-static
  重定义(只改方法体)之后,同一个对象 o:name() = v2-name,calc(5) = 500(已有的实例也用上了新方法体,字段 x 的值保持:1)
  再用另一版重定义 static 方法体:st() = v2-static,name() 变回 v1-name(每次重定义都是用你给的整份字节码替换)
  正在运行的 Slow.run()(在 sleep 的中途被重定义):这次调用返回 旧代码前半 + 旧代码后半(已经在执行的那次仍然跑完旧代码);重定义之后新的调用 Slow.run() = 新代码前半 + 新代码后半

分步演示

HotSwap
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,标准的 HotSpot 重定义(IDE 的调试器使用的是同类机制)
    • 没有验证:DCEVM/JetBrains Runtime 等增强版热替换(能增删方法与字段)、retransformClasses 与 transformer 的配合、重定义对已编译方法(JIT)的去优化、正在执行方法的栈帧替换(OSR)、嵌套类与 lambda 的重定义;每个用例只测了一次
    京ICP备2026067502号-1京公网安备11010502063487号