一、背景
1.1 Netty AsciiString的性能回归
最近看Netty的提交,发现一个蛮有意思的提交(#17074),
提案的经过是这样的:
#13749 报告了
AsciiString.cached(String)的一个bug:该方法没有对输入进行Latin-1清洗,导致缓存的String与byte内容不一致。#17007 修复了这个bug,但引入了性能回归。修复后的实现对输入字符串进行了两次扫描:
第一次: new AsciiString(string)通过c2b(...)将char数组复制到byte数组;第二次: AsciiString.cached(String)再次扫描检查是否有非Latin-1字符。#17074 的提交者 dreamlike-ocean通过查看JIT汇编发现了问题的根源。他在PR中附上了两份
PrintAssembly的输出:jit-actual-old-c2-ascii-final.txt:旧版本的汇编,包含两个独立的循环;jit-actual-new-c2-ascii-final.txt:优化后的汇编,只有一个循环。
汇编输出清楚地表明:C2编译器没有自动合并这两个循环(Loop Fusion)。旧版本在AsciiString.cached中仍然保留了第二个扫描循环,而优化版本将c2b(...)转换和Latin-1检查合并到了一个循环中。
1.2 为什么汇编能揭示问题?
这个案例中,单纯观察源码其实也能发现潜在的问题。但通过汇编进行分析,确实会更加准确。汇编分析的价值在于:
验证假设:源码看起来是两次扫描,但JIT编译器可能会自动优化(如Loop Fusion)。汇编能告诉你编译器实际做了什么。 量化影响:汇编能直观展示循环结构、指令数量、向量化程度,帮助评估性能影响。 通用方法:不是所有性能问题都能从源码直接看出。对于内联决策、锁消除、向量化等深层优化,汇编是唯一的可靠证据。
此前,我仅在编写C++/Rust代码时尝试过通过汇编分析性能。编写Java应用代码,最多也就是借助JMH进行基准测试。这个案例激发了我探索JVM汇编优化的兴趣。
不过,当我尝试直接使用JDK打印汇编代码时,发现了问题——需要借助HSDIS插件,才能完成从Java源码到汇编输出的完整链路。
我们日常编写的Java代码会被JVM的JIT(Just-In-Time)编译器转化为机器码执行,要让JVM输出汇编,需要一个关键组件:HSDIS(HotSpot Disassembler)。这个库在大多数JDK发行版中并不自带,需要我们自己编译安装。
本文将记录从源码编译JDK和HSDIS,打通从Java源码到汇编输出的完整链路。
二、HSDIS 是什么
2.1 官方定义
根据OpenJDK Wiki(HotSpot/PrintAssembly)的描述:
HSDIS是HotSpot的一个插件,用于将动态生成的代码反汇编为可读的汇编指令。它实现了
PrintAssembly选项所要求的接口协议。
简单来说,JVM的JIT编译器生成的是原生机器码(二进制),而HSDIS是一个反汇编器插件,它调用底层的反汇编引擎(如Capstone、LLVM或GNU Binutils),将这些机器码翻译成人类可读的汇编文本。
2.2 为什么需要它
JVM本身只生成机器码,不会直接输出可读的汇编文本。当你使用-XX:+PrintAssembly参数时,JVM会查找HSDIS库,如果找不到就会报错:
Loading hsdis library failed没有HSDIS,PrintAssembly就无法生效。
2.3 HSDIS的独立化演进
在JDK 16之前,HSDIS的源码位于HotSpot仓库内部(hotspot/src/share/tools/hsdis/),与JVM源码紧密耦合。
从JDK 17开始,HSDIS被重构并独立到了JDK源码树的 src/utils/hsdis/ 目录下。根据其README 的说明:
The files in this directory are built independently of the HotSpot JVM.
hsdis is an interface exposed by Hotspot. There are several backends that implement this interface, using different disassembly engines. Included in the JDK is support for building hsdis with Capstone, LLVM or GNU binutils. The interface is fairly straightforward and easy to implement using other backends.
关键变化:
HSDIS现在是一个独立构建的组件,不再需要重新编译整个JVM; 支持三种后端引擎:Capstone、LLVM、GNU Binutils; 通过 configure --with-hsdis=<backend>选择后端;使用 make build-hsdis单独构建,make install-hsdis安装到JDK。
2.4 三种后端对比
| Capstone | sudo apt install libcapstone-dev | |
| LLVM | sudo apt install llvm | |
| GNU Binutils |
这里因为仅仅只是探索,所以选择使用Capstone(安装简单,依赖少,且无许可证问题)。
三、环境准备
为什么选择WSL?
在Windows下编译JDK和HSDIS是一件比较麻烦的事情:
依赖工具链复杂:需要安装Visual Studio、Cygwin或MSYS2等工具,配置过程繁琐; 编译脚本兼容性问题:JDK的构建脚本主要针对Linux/macOS优化,Windows下经常遇到路径、换行符等兼容性问题; HSDIS编译更困难:HSDIS需要链接底层反汇编引擎(如Capstone),在 Windows下找到合适的预编译库并不容易。
相比之下,在WSL (Windows Subsystem for Linux) 下编译就简单得多:
完整的Linux工具链(gcc、make、autoconf 等)开箱即用; 包管理器(apt)可以一键安装依赖; JDK 构建脚本在Linux下经过充分测试,稳定性有保障。
本文以WSL (Ubuntu) + JDK 25为例。
3.1 安装基础依赖
sudo apt updatesudo apt install -y build-essential autoconf git zip unzip \ libcapstone-dev pkg-config3.2 安装 Bootstrap JDK
编译JDK需要一个已有的JDK作为引导(boot JDK),版本要求通常为N或N-1:
sudo apt install -y openjdk-25-jdk# 验证java -version# openjdk version "25.0.3" 2026-04-21四、编译JDK(含 HSDIS)
4.1 获取源码
mkdir -p ~/jdk-build && cd ~/jdk-build# 克隆 JDK 25 源码(以 tag jdk-25+36 为例)git clone --depth 1 --branch jdk-25+36 \ https://github.com/openjdk/jdk.git jdk-jdk-25-36cd jdk-jdk-25-364.2 配置构建
bash configure \ --with-hsdis=capstone \ --with-capstone=/usr \ --with-debug-level=release \ --disable-warnings-as-errors \ --with-boot-jdk=/usr/lib/jvm/java-25-openjdk-amd64参数说明:
--with-hsdis=capstone | |
--with-capstone=/usr | |
--with-debug-level=release | |
--disable-warnings-as-errors | |
--with-boot-jdk=... |
配置成功后会看到类似输出:
checking what hsdis backend to use... 'capstone'checking for capstone... /usr4.3 编译
# 编译整个JDK(含HSDIS)make images JOBS=$(nproc)# 如果只想单独编译HSDISmake build-hsdis4.4 验证HSDIS
编译完成后,HSDIS库位于:
build/linux-x86_64-server-release/jdk/lib/hsdis-amd64.so验证是否可用:
build/linux-x86_64-server-release/images/jdk/bin/java \ -XX:+UnlockDiagnosticVMOptions \ -XX:+PrintAssembly \ -version如果输出汇编代码(而不是Loading hsdis library failed),说明HSDIS已生效。
4.5 安装到系统JDK
如果不想使用编译出的JDK,可以将HSDIS复制到系统JDK的lib目录:
sudo cp build/linux-x86_64-server-release/jdk/lib/hsdis-amd64.so \ /usr/lib/jvm/java-25-openjdk-amd64/lib/# 验证java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -version五、HSDIS的接口协议
HSDIS的核心接口非常简洁,定义在hsdis.h中:
/* decode_instructions -- dump a range of addresses as native instructions This implements the protocol required by the HotSpot PrintAssembly option. The start_va, end_va is the virtual address the region of memory to disassemble and buffer contains the instructions to decode. The option string, if not empty, is interpreted by the disassembler implementation. The printf callback is 'fprintf' or any other workalike. It is called as (*printf_callback)(printf_stream, "some format...", some, format, args). The event callback receives an event tag (a string) and an argument (a void*).*/voiddecode_instructions(void* start_va, void* end_va, decode_func_printf_callback printf_callback,void* printf_stream, decode_func_event_callback event_callback,void* event_stream,constchar* options);JVM调用这个函数,传入一段内存地址范围(即JIT生成的机器码),HSDIS通过回调函数输出反汇编结果。这就是为什么切换后端(Capstone/LLVM/Binutils)只需替换一个.so文件——接口是统一的。
六、常用JVM诊断参数
HSDIS安装好后,配合以下JVM参数使用:
java -XX:+UnlockDiagnosticVMOptions \ -XX:+PrintAssembly \ -XX:+PrintInlining \ -XX:+PrintCompilation \ -XX:CompileCommand=print,com/example/MyClass.myMethod \ -jar app.jar-XX:+UnlockDiagnosticVMOptions | |
-XX:+PrintAssembly | |
-XX:+PrintInlining | |
-XX:+PrintCompilation | |
-XX:CompileCommand=print,<method> | |
-XX:CompileCommand=dontinline,<method> |
七、常见问题
7.1 编译时找不到Capstone
configure: error: Could not find capstone确保安装了libcapstone-dev(注意是-dev包,不是运行时包)。
7.2 HSDIS加载成功但没有汇编输出
可能是方法还没有被JIT编译。尝试:
增加预热次数( -Xcomp强制编译,但不推荐用于性能测试);使用 -XX:+PrintCompilation确认方法是否被编译.
7.3 能否在生产环境使用HSDIS?
不推荐。HSDIS是诊断工具,PrintAssembly会显著影响启动时间和运行性能。仅在开发/测试环境使用。
八、总结
HSDIS是JVM PrintAssembly功能的反汇编插件从JDK 17开始独立构建,支持 Capstone/LLVM/Binutils三种后端; 通过 configure --with-hsdis=capstone+make images即可完成编译编译完成后将 hsdis-amd64.so放入JDK的lib目录即可使用
下一步,我们将使用这个工具,通过实际案例深入分析JIT汇编,包括方法内联、SIMD向量化、锁消除等JVM优化的底层表现。
参考资料
OpenJDK Wiki: PrintAssembly JDK 源码: src/utils/hsdis/README.md HSDIS 源码接口: hsdis.h
夜雨聆风