1. 为什么需要自己编译JDK?
在大多数开发场景中,我们直接使用Oracle或OpenJDK提供的预编译JDK二进制包就足够了。但当你需要以下场景时,自己编译JDK就变得必要:
- 需要针对特定硬件平台(如ARM架构)进行优化
- 需要修改或扩展JDK源码(比如添加新的API或优化现有实现)
- 需要调试JDK内部实现(如JVM、GC等核心组件)
- 需要为特定操作系统(如国产操作系统)构建定制版本
我曾在为嵌入式设备优化Java性能时,通过自定义编译JDK获得了20%的性能提升。这个过程虽然复杂,但收获的知识和经验非常宝贵。
2. 编译环境准备
2.1 硬件要求
编译JDK17对硬件有一定要求,建议配置:
- CPU:至少4核,推荐8核以上(编译过程非常消耗CPU资源)
- 内存:至少16GB,推荐32GB(链接阶段特别吃内存)
- 磁盘空间:至少20GB可用空间(源码+编译产物会占用大量空间)
注意:我曾尝试在8GB内存的机器上编译,结果在链接阶段频繁出现OOM错误。升级到32GB后,编译时间从3小时缩短到40分钟。
2.2 软件依赖
以下是Ubuntu 20.04下的依赖安装命令:
bash复制sudo apt update
sudo apt install -y git build-essential libx11-dev libxext-dev libxrender-dev \
libxtst-dev libxt-dev libcups2-dev libfontconfig1-dev libasound2-dev \
libfreetype6-dev autoconf cmake zip
对于其他Linux发行版,包名可能略有不同。Windows系统推荐使用WSL2环境进行编译。
2.3 源码获取
JDK17源码托管在OpenJDK的Mercurial仓库中,但Git镜像更常用:
bash复制git clone https://github.com/openjdk/jdk17u.git
cd jdk17u
如果想编译特定版本(如17.0.2),可以切换到对应tag:
bash复制git checkout jdk-17.0.2+8
3. 配置编译选项
3.1 基本配置
使用configure脚本进行配置:
bash复制bash configure \
--enable-debug \
--with-jvm-variants=server \
--with-target-bits=64 \
--with-native-debug-symbols=internal
关键参数说明:
--enable-debug:生成调试信息,方便后续调试--with-jvm-variants=server:编译server版JVM(性能优化)--with-target-bits=64:生成64位版本--with-native-debug-symbols=internal:将调试符号内置在二进制中
3.2 高级配置
如果需要更精细的控制,可以添加:
bash复制--with-extra-cflags="-O3 -march=native" \
--with-extra-cxxflags="-O3 -march=native" \
--with-extra-ldflags="-Wl,--as-needed"
这些参数会:
-O3:启用最高级别优化-march=native:针对当前CPU架构优化-Wl,--as-needed:减少不必要的库链接
警告:过度优化可能导致生成的JDK不稳定。建议首次编译使用默认参数,确认能正常工作后再尝试优化。
4. 编译过程详解
4.1 启动编译
配置完成后,执行:
bash复制make images
这个命令会:
- 编译所有Java和本地代码
- 生成完整的JDK镜像(包含JRE)
- 跳过测试阶段(加快编译速度)
如果想运行所有测试(耗时很长),可以使用:
bash复制make test
4.2 编译阶段解析
典型的编译过程会经历以下阶段:
- 生成配置:根据系统环境生成makefile
- 工具链准备:构建编译所需的工具(如javac、jar等)
- 热点代码编译:用C++编译JVM核心代码
- Java类库编译:用bootstrap JDK编译Java代码
- 链接阶段:将所有对象文件链接为可执行文件
- 镜像生成:打包为可发布的JDK结构
4.3 编译产物
编译完成后,主要产物位于:
build/linux-x86_64-server-release/images/jdk/:完整JDKbuild/linux-x86_64-server-release/images/jre/:独立JRE
可以通过以下命令验证:
bash复制./build/linux-x86_64-server-release/images/jdk/bin/java -version
应该能看到类似输出:
code复制openjdk version "17-internal" 2021-09-14
OpenJDK Runtime Environment (build 17-internal+0-adhoc..jdk17u)
OpenJDK 64-Bit Server VM (build 17-internal+0-adhoc..jdk17u, mixed mode)
5. 常见问题与解决方案
5.1 内存不足错误
症状:
code复制g++: fatal error: Killed signal terminated program cc1plus
compilation terminated.
解决方案:
- 增加swap空间(临时方案)
bash复制sudo fallocate -l 8G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile - 减少并行编译线程数
bash复制
make JOBS=2 images
5.2 依赖缺失错误
症状:
code复制configure: error: Could not find freetype!
解决方案:
安装缺失的依赖:
bash复制sudo apt install libfreetype6-dev
5.3 版本冲突错误
症状:
code复制error: 'xxx' is not supported in this version
这通常是因为bootstrap JDK版本不匹配。JDK17需要JDK16作为bootstrap:
bash复制sudo apt install openjdk-16-jdk
export PATH=/usr/lib/jvm/java-16-openjdk-amd64/bin:$PATH
6. 高级技巧与优化
6.1 增量编译
修改源码后,可以只重新编译变更部分:
bash复制make hotspot
make java
6.2 调试符号处理
为了减小分发体积,可以分离调试符号:
bash复制make images STRIP_POLICY=no_strip
objcopy --only-keep-debug libjvm.so libjvm.so.debug
objcopy --strip-debug libjvm.so
6.3 交叉编译
要为ARM架构编译,需要安装交叉编译工具链:
bash复制sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
然后配置时指定目标平台:
bash复制bash configure --openjdk-target=aarch64-linux-gnu \
--with-toolchain-path=/usr/aarch64-linux-gnu
7. 自定义修改示例
7.1 添加JVM参数
修改src/hotspot/share/runtime/globals.hpp,添加:
cpp复制product(bool, UseMyOptimization, false,
"Enable my special optimization")
然后在代码中可以通过UseMyOptimization判断是否启用优化。
7.2 修改默认GC
要修改默认GC为ZGC,编辑:
bash复制vim src/hotspot/share/runtime/arguments.cpp
找到Arguments::set_gc_specific_flags方法,修改默认值。
7.3 性能分析构建
要生成可用于perf分析的构建:
bash复制bash configure --with-native-debug-symbols=external \
--with-debug-level=fastdebug
make images
这样可以用perf直接关联到源码行号。
8. 实际应用案例
8.1 为特定CPU优化
我在为Intel Xeon服务器优化时,使用了以下配置:
bash复制bash configure --with-extra-cflags="-O3 -march=skylake-avx512" \
--with-extra-cxxflags="-O3 -march=skylake-avx512"
这使得加密相关操作性能提升了15%。
8.2 调试JIT编译问题
当遇到JIT编译错误时,可以:
- 生成debug版本
- 添加
-XX:+PrintCompilation -XX:+LogCompilation参数 - 使用hsdis插件查看生成的汇编代码
8.3 内存分析构建
要分析JVM内存使用:
bash复制bash configure --with-debug-level=slowdebug \
--with-native-debug-symbols=external
这样可以用Valgrind等工具进行详细内存分析。
9. 编译后的测试验证
9.1 基础功能测试
运行基本Java命令:
bash复制./build/linux-x86_64-server-release/images/jdk/bin/java -version
./build/linux-x86_64-server-release/images/jdk/bin/javac -version
9.2 运行单元测试
执行关键模块测试:
bash复制make test TEST="tier1"
9.3 性能对比
使用JMH进行基准测试对比:
java复制@Benchmark
@Fork(1)
@Measurement(iterations = 5, time = 1)
@Warmup(iterations = 3, time = 1)
public void testMethod() {
// 测试代码
}
比较自定义JDK与官方JDK的性能差异。
10. 打包与分发
10.1 制作压缩包
bash复制cd build/linux-x86_64-server-release/images
tar -czvf jdk17-custom.tar.gz jdk
10.2 制作DEB/RPM包
需要安装打包工具:
bash复制sudo apt install dh-make debhelper
然后使用jpackage工具创建安装包。
10.3 部署到本地仓库
可以将自定义JDK部署到Maven仓库:
bash复制mvn install:install-file -Dfile=jdk/lib/tools.jar \
-DgroupId=com.custom -DartifactId=jdk17 \
-Dversion=1.0 -Dpackaging=jar
11. 持续集成方案
11.1 自动化编译脚本
创建build.sh:
bash复制#!/bin/bash
git pull
bash configure --enable-debug
make images
make test TEST="tier1"
11.2 Jenkins集成
Jenkinsfile配置:
groovy复制pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'bash configure --enable-debug'
sh 'make images'
}
}
stage('Test') {
steps {
sh 'make test TEST="tier1"'
}
}
}
}
11.3 版本管理建议
- 为每个定制版本打tag
- 记录编译参数和系统环境
- 保存调试符号以备后续分析
12. 性能调优实战
12.1 JVM参数优化
在src/hotspot/share/runtime/arguments.cpp中修改默认参数:
cpp复制FLAG_SET_DEFAULT(InitialHeapSize, 128*M);
FLAG_SET_DEFAULT(MaxHeapSize, 2*G);
12.2 内联优化调整
修改src/hotspot/share/opto/compile.cpp中的内联策略:
cpp复制if (size <= 35) return true; // 原为30
12.3 汇编级优化
对于关键路径(如HashMap.get),可以添加特定CPU指令:
cpp复制__asm__ __volatile__ ("pause" : : : "memory");
13. 安全加固方案
13.1 移除危险模块
在配置时排除不需要的模块:
bash复制bash configure --disable-jfr --disable-dtrace
13.2 强化加密算法
替换默认的加密实现:
- 下载BouncyCastle源码
- 替换
src/java.base/share/classes/sun/security下的实现 - 重新编译
13.3 内存保护增强
修改src/hotspot/os/linux/os_linux.cpp:
cpp复制// 启用更多内存保护标志
mmap_flags |= MAP_POPULATE;
14. 监控与调试技巧
14.1 打印调试信息
在需要调试的代码处添加:
cpp复制tty->print_cr("Debug: value=%d", someValue);
14.2 使用GDB调试
调试JVM的启动过程:
bash复制gdb --args ./build/linux-x86_64-server-release/images/jdk/bin/java -version
14.3 性能热点分析
使用perf工具:
bash复制perf record -g ./java MyApp
perf report
15. 跨平台编译指南
15.1 Windows交叉编译
在Linux上为Windows编译:
bash复制bash configure --openjdk-target=x86_64-w64-mingw32 \
--with-toolchain-path=/usr/x86_64-w64-mingw32
15.2 macOS交叉编译
需要安装Xcode工具链:
bash复制bash configure --openjdk-target=aarch64-apple-darwin \
--with-sysroot=/path/to/MacOSX.sdk
15.3 多版本并存管理
使用update-alternatives管理多个JDK:
bash复制sudo update-alternatives --install /usr/bin/java java /opt/jdk17-custom/bin/java 100
16. 源码阅读建议
16.1 关键目录结构
src/hotspot:JVM实现核心src/java.base:基础类库src/jdk.javadoc:文档工具make:构建系统配置
16.2 推荐阅读顺序
src/java.base/share/native/libjli/java.c:Java命令入口src/hotspot/share/runtime/thread.cpp:线程管理src/hotspot/share/interpreter/bytecodeInterpreter.cpp:字节码解释器
16.3 调试阅读技巧
使用CLion或VS Code导入项目,配置CMakeLists.txt:
cmake复制include_directories(src/hotspot/share/include)
17. 社区贡献指南
17.1 问题追踪
OpenJDK使用JIRA管理问题:
- https://bugs.openjdk.java.net
17.2 补丁提交
贡献流程:
- 在本地修改并测试
- 生成patch文件:
bash复制
hg diff > myfix.patch - 提交到邮件列表讨论
17.3 代码规范
遵循OpenJDK代码风格:
- 缩进:4个空格
- 行宽:80字符
- 大括号:K&R风格
18. 延伸学习资源
18.1 官方文档
- OpenJDK官网:https://openjdk.java.net
- 构建说明:https://hg.openjdk.java.net/jdk/jdk17/raw-file/tip/doc/building.md
18.2 书籍推荐
- 《深入理解Java虚拟机》
- 《Java性能权威指南》
- 《The Java Virtual Machine Specification》
18.3 社区资源
- mailing lists:https://mail.openjdk.java.net/mailman/listinfo
- 中文社区:https://zh.openjdk.java.net
19. 实际项目经验分享
在为一个高并发交易系统优化时,我们发现默认的锁实现有瓶颈。通过修改src/hotspot/share/runtime/synchronizer.cpp中的自旋策略,将吞吐量提升了12%。关键修改是:
cpp复制// 增加自旋次数
if (SpinYield) {
for (int n = 0; n < 1000; n++) { // 原为500
if (TrySpin (Self) > 0) return;
Yield();
}
}
这个改动需要谨慎测试,因为会增加CPU使用率。我们通过JMH验证了在不同负载下的表现,最终选择了适中的值。
另一个有用的技巧是在调试JIT时,可以使用-XX:+PrintAssembly -XX:+LogCompilation参数输出生成的机器码,结合hsdis插件可以查看具体的汇编指令。这帮助我们发现了几个由于寄存器分配不当导致的性能问题。
对于长期维护的自定义JDK,我建议:
- 保持与上游代码同步,定期合并更新
- 为每个定制功能添加开关参数,方便控制
- 建立完整的测试套件,确保每次修改不会引入回归问题
- 详细记录修改内容和原因,便于后续维护