氛围编程时代的运行时代码分析
“氛围编程”时代的运行时代码分析
在**“氛围编程”**时代——即在短时间内引入或重构大量代码,通常借助大语言模型(LLM)的帮助——你需要关于新逻辑实际执行情况的即时反馈。不需要全面的分析。不需要纳秒级的精确计时。只需要快速确认你的循环没有比应有的次数多运行 10,000 倍。
然而,对于快速验证而言,传统的性能分析器可能显得有些杀鸡用牛刀。此外,它们以方法/堆栈为粒度展示结果,需要进行上下文切换才能解读。它们还会引入开销,从可忽略不计(例如 JFR/采样)到显而易见(调用跟踪/插桩)不等。因此,作为快速迭代期间的常驻反馈,它们不太方便。
jvm-hotpath 是专为这种工作流程构建的轻量级 Java Agent。它直接在你的源代码中显示每行执行计数,确切地告诉你哪些行运行了以及运行频率——就在你的应用程序运行时。
这有何不同
零计时开销。 仅计数,不进行纳秒级测量。
统计每次执行。 无采样,不会遗漏快速方法。
对 LLM 友好。 你可以将其传输给 LLM 进行分析的 JSON 报告。
实时更新。 JSONP 轮询让你可以实时观察计数更新。无需服务器。
现代 Java。 在 CI 中针对 Java 11、17、21、23 和 24 进行了测试。它也适用于 Spring Boot 和 Micronaut。
Java 工具链的空白
最初的问题
直接的痛点很简单:代码生成的速度超过了你构建心智模型的速度。几年前,我在一个继承的系统中遇到了同样的核心问题。所以我改造了 Cobertura——一个覆盖率工具——将其用作运行时分析工具。通过对应用程序进行插桩并执行特定行为,我可以在事后观察执行计数。结果,我得到了代码库的运行时形态心智地图——以及一个可以自信地进行更改的切入点。
毕竟,静态分析告诉你可能执行什么。测试告诉你应该执行什么。我需要的是看到在实际工作负载下确实执行了什么。
现有工具为何不适用
Cobertura 的最后一个版本发布于 2015 年,因此它不适合现代 Java 工具链。此后,没有广泛采用且积极维护的工具专注于实时的每行执行频率。
覆盖率工具(例如 JaCoCo)跟踪代码是否执行,而不是执行了多少次。性能分析器显示 CPU 时间消耗在哪里。两者都无法显示实际条件下的执行频率。
我最终为何决定构建它
现代 Java 工具链已经朝着不同的方向发展,但这个想法一直萦绕在我心头。所以我评估了可用的工具。例如,OpenClover 的“完全支持”版本是 Java 17,较新版本被列为实验性版本。同样,JCov 作为 OpenJDK CodeTools 项目存在,但设置比较老派。简而言之,没有简单的“从 Maven Central 拉取一个 jar 包就能用”的路径。IntelliJ 的内置覆盖率功能对于覆盖率来说非常出色,它将运行数据存储为 IDE 覆盖率套件(例如 .ic)。然而,它仍然是 IDE 中心的工作流程。简而言之,这不是你可以在 CI 构件中重用或作为独立实时报告共享的东西。
在一个小时的死胡同之后,Claude 直奔主题:
“你想让我帮你从源代码构建 JCov,还是我为你创建一个简单的自定义执行计数器?”
最终,那个问题决定了方向。
真实世界的 Bug
Bug 是如何出现的
这个工具诞生于一次高速的“氛围编程”会话中。具体来说,我正在重构一个核心处理引擎。标准的性能分析器遗漏了这个 Bug。系统感觉还不慢:
Bug: 一个 .filter(r -> r.isDuplicate()) 调用在 15 秒内执行了 1900 万次。
问题: 每次调用约 50 纳秒——采样分析器很容易将其采样不足。
影响: O(N²) 而不是 O(1) 藏在眼皮底下。
为何难以发现
换句话说,过滤器位于循环内部,而不是只计算一次。这是一个经典的错误。然而,传统工具无法看到它。相反,我希望对实际运行的内容获得即时的运行时可见性。
执行计数让这一点变得显而易见。例如,在单行代码旁边看到“19,147,293 次执行”消除了所有歧义。不需要计时数据,也不需要解读。
核心洞察:频率 ≠ 资源消耗
Java 性能分析器专注于资源消耗:CPU 时间、内存分配、线程争用。相比之下,jvm-hotpath 显示代码运行了多少次(频率)。
在现代 Java 中,这种区别很重要。例如,JIT 编译使单个调用变得很快。因此,瓶颈通常是算法层面的——O(N) 与 O(1) 的区别。此外,逻辑错误可能会产生数百万次不必要的调用。而且,采样分析器是基于统计学的。此外,非常短但频繁的工作很容易被采样不足。
它是“逻辑 X 光”,而不是“资源监视器”。
工作原理
插桩
jvm-hotpath 是一个 Java Agent,它在类加载时使用 ASM 对字节码进行插桩。具体来说,它在每个可执行行之前插入一个计数器。确实,没有采样,没有计时——只有频率。
因此,开销足够低,适合正常的开发运行。
报告
收集的数据被写入一个交互式 HTML 报告,该报告在你的应用程序运行时刷新。具体来说,它显示带有语法高亮的源代码,每行旁边都有执行计数。此外,全局热力图使热路径在视觉上更加突出。
通过 JSONP 驱动的轮询,你可以直接从磁盘(file://)打开报告并实时观察其更新。无需服务器。
值得注意的是,这种狭窄的关注是故意的。没有火焰图,没有仪表板,没有事后跟踪——只有映射到源代码的行级执行频率。
机器可读输出
Agent 还会写入 execution-report.json。因此,它为你提供了一个机器可读的构件,你可以将其输入到 CI 步骤或基于 LLM 的工具中。
实际效果演示:
https://github.com/user-attachments/assets/cc89451b-a41f-491e-a1f6-8e87328979c0
入门指南
Maven 插件(推荐)
将插件添加到你的 pom.xml:
然后使用激活的 agent 运行你的应用程序:
对于 exec:exec,你需要一个主类。传递 -Dexec.mainClass=... 或在 pom.xml 中配置 exec.mainClass。
报告将在 target/site/jvm-hotpath/execution-report.html 生成。
对于多模块项目或生成的代码(OpenAPI/MapStruct),插件可以将多个源根合并到一个报告中。此外,你可以通过 sourcepath 直接传递依赖源代码存档。
手动使用 Agent
如果你更喜欢直接控制,请运行:
关键参数: packages 设置要插桩的包。sourcepath 指向源根或存档(.jar、.zip)。flushInterval 控制报告刷新之间的秒数(0 = 不自动刷新)。verbose 打印带有可点击文件 URL 的插桩详细信息。
独立重新生成报告
如果你已经有来自 CI 的 execution-report.json,则可以在不重新运行应用程序的情况下重新生成 HTML:
这不是什么
它不是覆盖率百分比工具——请使用 JaCoCo。它也不是 CPU 计时分析器——请使用 JFR 或 async-profiler。最后,它不是 24/7 生产监控系统。
超越性能:死代码与认知负荷
执行计数使发现死代码和很少使用的分支变得容易。此外,它们揭示了主要出于历史原因而存在的功能。而且,它们减少了认知负荷。当你知道哪些部分实际运行时,推理更改、自信重构或决定暂时不考虑什么就会变得容易得多。
确实,对于任何使用 AI 辅助工具快速工作的人来说,这种清晰度是非常宝贵的。
关于构建方式的说明
第一个原型诞生于 AI 辅助的“氛围编程”,主要是与 Claude 一起。随后,我结合了手动工作以及 Codex 和 Gemini 的帮助进行了迭代。我还针对真实的 JVM 工作负载验证了一切。
总的来说,这些工具加速了探索。尽管如此,动机和方向来自于真实代码库中的实际使用。
未来方向
有明显的下一步——Gradle 改进、更好的排除控制、更广泛的框架测试。不过就目前而言,我故意保持范围较小。确实,这是我的第一个开源版本。
真正的问题更简单:这是否能帮助你更快、更自信地理解你的代码库?
项目: github.com/sfkamath/jvm-hotpath
文档: 完整 README
动机: 深入了解原因
本文 Runtime Code Analysis in the Age of Vibe Coding 首次发布于 foojay。