资讯动态

Windows 11下编译OpenJDK 21:从工具链到make images的完整指南

发布时间:2026/10/20 8:56:18 来源:尧图企业网站定制
说来有点打脸我在Windows 11上编译OpenJDK 21这件事前后折腾了一整个周末。最初想象得很简单——下载源码、跑两条命令、完事。结果configure阶段就让我体会到什么叫“一行报错信息背后藏着三个准备不周”。如果你也想在Windows 11下面自己编译一份OpenJDK 21想搞明白JVM内部实现、修改某些默认行为或者纯粹好奇Java平台的构建系统是怎么工作的这篇文章大概能让你少走两天弯路。我会按自己的实际路径把从工具链选型到最终跑通make images的每一个关键环节都摊开来讲包括配置参数的含义、报错的排查套路、以及几处文档里没有细说的坑。1. 为什么放着现成的JDK不用非要自己编译这个问题的答案决定了你有没有必要往下看。老实说99%的应用开发者确实不需要自己编译JDK直接下载官方二进制包就好了。但剩下那1%的人迟早会碰到官方发行版解决不了的场景。1.1 官方发行版解决不了的刚需最常见的刚需是调试JVM的native代码。Java层面的问题用IDE调试就行但一旦问题深入JIT编译器、GC实现、类加载器的native层你就必须要有一份带调试符号、且源码路径跟你本机对应的JDK。官方发行版虽然也会发布调试符号但符号与源码版本严格一一对应你想在某个HotSpot源码文件里加两行日志再跑一次官方包根本做不到。另一个刚需是定制JVM行为。比如你想修改默认GC参数、调整某个内存管理的默认阈值、或者自己实验一个JIT优化选项这些都得改源码重新编译。我认识一个做性能分析的朋友为了观察某个GC行为在HotSpot源码里加了一段统计代码重新make images之后跑基准测试整个流程大概十几分钟就能完成一轮实验。这种节奏是二进制发行版给不了的。还有一类需求是验证新特性或学习底层机制。OpenJDK 21刚发布时不少人想试试虚拟线程和分代ZGC的实际运行效果同时希望能在本地代码里看到这些特性在JVM内部是怎么流转的。自己编译一份配合调试器单步跟踪理解深度完全不一样。1.2 编译一次能收获什么抛开功能诉求单说技术收获就很值。JDK的构建系统是GNU Make加autoconf的经典组合configure生成配置、make执行构建整个体系在开源界非常典型。你把这一套跑通对大型C/C项目的构建管理会有更实在的理解而不是只在终端里敲过./configure make而已。另外编译过程中你会被迫搞清楚不少Windows平台工具链的细节MSVC和MinGW的ABI差异、系统库的依赖、第三方库的编译接入方式、以及如何在Windows上模拟一个Unix-like的构建环境。这些东西单独学很枯燥但在“为了把JDK编译出来”的驱动下你会记得特别牢。1.3 什么人适合动手什么人建议放弃我建议满足以下任意一条的人再动手要改JVM源码并长期维护自定义分支想深入调试native层问题或者对构建系统有强烈好奇。反之如果只是想要一个“更新版本”的JDK那直接去下官方二进制包十分钟搞定不要在自己机器上浪费一整天。还有一点要提前说OpenJDK官方只把Windows上的MSVC作为受支持工具链MinGW虽然有尝试支持但不在官方保证范围内。你在网上会看到很多用MinGW编译成功的帖子但如果你想少踩坑请老老实实装Visual Studio的C工作负载。2. 构建前的三件套Boot JDK、MSYS2、Visual Studio整个准备阶段的核心就是这三样东西任何一样出了问题后面都走不通。我会把每一件的关键细节都讲清楚顺序也建议按我下面的来。2.1 Boot JDK版本的选择逻辑编译JDK需要一个“引导JDK”Boot JDK用来在构建过程中执行Java代码比如编译Java标准库源码、生成类文件等。官方要求构建JDK 21必须使用JDK 20或更新的版本。注意这里的边界逻辑不能用太老的否则构建工具无法运行也不建议用超出太多的版本尽管大部分情况下兼容但万一遇到某些代码生成逻辑对版本敏感排查起来很耗时。我的选择是下载一个JDK 20的官方二进制包比如20.0.2解压到一个短路径下比如C:\dev\bootjdk。这里有一个很实际的经验Windows的路径如果有空格在MSYS2的bash里会非常烦人每次传参都要加引号稍不留神就把路径拦腰截断。所以尽量把Boot JDK、源码目录、第三方库全部放到无空格的短路径下面。你可能会问能不能用已经发布的JDK 21二进制做Boot JDK可以构建系统通常不会拒绝但官方文档的建议是从上一个版本开始引导所以我在实际操作中一直用JDK 20作为Boot JDK稳。2.2 MSYS2出问题的重灾区OpenJDK的构建脚本体系是在Unix环境里设计的Windows下必须模拟一个基本的Unix shell环境MSYS2是目前最主流的选择。安装本身很简单从官网下载安装包一路下一步。但装完之后有几个关键坑。第一启动Shell时有多个入口常见的是“MSYS2 MSYS”和“MSYS2 MINGW64”。编译OpenJDK一定要用MSYS2 MSYS不要用MINGW64。因为MINGW64环境会自动把MinGW的bin目录加入PATH一旦configure检测到MinGW工具链可能会误判工具链类型甚至给你报一个“MinGW is not supported”的错。别问我怎么知道的。第二进入MSYS2 shell后第一件事是更新系统pacman -Syu更新完如果提示关闭终端重开就重开一下可能需要执行两轮才能完全更新干净。第三安装构建需要的工具包pacman -S autoconf zip unzip make file diffutils findutils这些工具看着不起眼缺一个都会导致configure中途报错。尤其是zip和unzipOpenJDK在打包镜像时要靠它们来压缩、解压各类资源。file工具用来识别二进制文件类型也会被configure检查。2.3 Visual Studio的正确安装姿势Windows下唯一受官方支持的C编译器是MSVC也就是Visual Studio里的那套工具链。我用的版本是Visual Studio 2022 Community免费版完全够用。安装时的工作负载选择很关键必须勾选“使用C的桌面开发”Desktop development with C它会把MSVC编译器、Windows SDK、CMake等一堆组件一起装进来。另外建议在右侧“安装详细信息”里确认Windows 11 SDK已经被选中如果没有手动补上。装完之后理论上configure可以通过vswhere自动检测到Visual Studio的位置不需要手动配置。但如果你的机器上装了多个版本的VS或者安装路径比较特殊configure偶尔会找不到。这种情况下可以设置环境变量VSINSTALLDIR把它指到VS的安装根目录。比如export VSINSTALLDIRC:/Program Files/Microsoft Visual Studio/2022/Community注意在MSYS2的bash里Windows风格路径用正斜杠。顺带提醒不要为了图省事去装MinGW-w64替代MSVC。OpenJDK官方不支持此路线你会在编译链接阶段遇到一堆莫名其妙的ABI问题。3. configure参数和坑的第一次集中爆发准备阶段做完进入真正的“第一次集中爆发点”——configure。这一步负责检查系统、探测工具链、生成构建配置。绝大多数失败都发生在这里但也正因为失败得早问题通常不难修。3.1 我的configure命令模板先拉取源码。进入MSYS2 MSYS shell之后切换到你准备的代码目录git clone --depth 1 --branch jdk-2135 https://git.openjdk.org/jdk cd jdkjdk-2135是OpenJDK 21的GA版本标签。如果官方站点访问慢也可以找代码托管镜像下载release的tar.gz包解压后效果一样。我的configure命令长这样export MSYS2_ARG_CONV_EXCL* bash configure \ --with-boot-jdk/c/dev/bootjdk \ --with-freetype-src/c/dev/freetype-src \ --with-jobs8逐个解释。MSYS2_ARG_CONV_EXCL*是我踩过一次大坑之后养成的习惯。MSYS2默认会把某些看起来像路径的参数自动转换成Windows格式或Unix格式这种“贴心”在复杂的configure里反而会引入混乱。设置这个环境变量等于告诉MSYS2不要自动转换任何参数让configure脚本自己处理路径。--with-boot-jdk指向Boot JDK的解压目录必须是你准备阶段放的那个短路径。--with-freetype-src指向freetype的源码目录。OpenJDK的java.desktop模块渲染字体时依赖freetypeWindows下用MSVC工具链时不能直接用MSYS2里为MinGW编译的freetype库因为ABI不兼容。最简单可靠的方式就是下载freetype官方源码包解压后指定源码路径构建系统会在构建过程中自己编译一个可用的版本。这一步在缺乏图形界面依赖的开发服务器上也很实用。--with-jobs8是编译时的并行线程数。如果你的CPU是16核可以设成16但前提是内存跟得上我后来会详细说。3.2 每次configure都该检查的五件事跑完configure之后它会输出一个摘要报告列出各项检测结果。我强烈建议你每次都在这个报告里确认五件事缺一不可。第一确认“Boot JDK”正确识别为JDK 20并且路径没有解析出额外的反斜杠或空格污染。第二确认“Toolchain”显示为microsoft或带msvc字样而不是gcc。第三确认freetype状态是ok不是not found或者failed。第四确认build configuration里平台是windows-x86_64-server-release这样的正常格式。第五确认没有大面积warning级别的失败项尤其是external library dependencies部分。这五件事里只要有一件不对先修好再往下走不要抱着“先make试试看”的心态。configure阶段的问题在make阶段通常会被放大三倍到时候定位起来痛苦得多。另外configure的报错信息大部分会给出明确的提示比如缺某个工具时说“Cannot find zip”缺少编译器时说“No supported C compiler found”。如果你看到这类信息先对照我前面准备的工具清单补装不要盲目改configure参数绕过检测。绕过检测一时爽make的时候往往更惨。4. make images正式编译与提速经验configure成功之后一切才刚开始。真正耗时的是make images它会在build目录下生成一个完整的JDK镜像包括bin目录、lib目录、conf目录以及最关键的JVM本体。4.1 第一轮编译的正确姿势第一次编译建议不要加太多花哨参数直接用make images如果你的configure阶段指定了--with-jobs8make默认就会用8个线程。如果没有指定可以在make命令里临时加make images -j8整个编译过程分几个阶段先是编译hotspot的C代码然后编译Java模块再链接native库最后打包JDK镜像。hotspot编译是最耗时的阶段几百个C文件一个接一个地编译期间CPU会长时间跑满。在8核16GB内存的机器上第一轮全量构建一般需要30到60分钟。如果你的机器是4核8GB建议把jobs设在2到4宁可慢一点也别中途被内存耗尽拖垮。我见过有人直接把jobs设成16结果编译到hotspot中段时内存耗尽编译器进程被杀整轮构建报废前面的时间全白费。首次构建成功后build目录下会出现build/windows-x86_64-server-release/images/jdk这个目录就是成品JDK。用里面的bin\java.exe运行-version你会看到类似这样的输出openjdk version 21 2023-09-19 OpenJDK Runtime Environment (build 2135-mybuild) OpenJDK 64-Bit Server VM (build 2135-mybuild, mixed mode, sharing)注意那个mybuild这是因为我当时编译时在configure里加了--with-version-optmybuild。如果你不加这里就显示官方风格的空版本后缀。4.2 如何显著缩短编译时间第一次全量构建成功之后后续的增量构建会快很多几分钟到十几分钟就能完成。但如果你要反复做全量构建或者机器配置一般有四个提速手段非常有效。第一个手段是调整并行度。--with-jobs或者make的-j参数是立竿见影的但要关注内存。hotspot里有些单个C文件编译时能吃到1GB以上的内存jobs数量乘以1.5GB做个粗略估算别超过物理内存。第二个手段是关闭Windows Defender对构建目录的实时扫描。这一步对速度影响之大超出很多人的预期。Windows Defender会在每个新生成的exe、dll被写入时进行实时扫描而JDK构建会生成成千上万个文件扫描开销积少成多能把构建时间拉长一倍。解决办法是在Windows安全中心的“病毒和威胁防护”里把源码目录和build目录都加入排除列表。第三个手段是减少不必要的JVM特性。configure支持通过--with-jvm-features控制编译进的组件比如不需要分代ZGC或Shenandoah时可以显式剔除bash configure --with-jvm-features-zgc,-shenandoahgc这样能减少一部分C代码的编译量。不过我要提醒如果要拿这份JDK做性能对比实验建议保留所有GC否则结果不完整。第四个手段是使用SSD。机械硬盘上全量构建OpenJDK是折磨大量随机小文件读写会显著拖慢速度。如果条件允许一定在SSD上构建。5. 编译失败排查我的问题清单和解决路径编译OpenJDK不遇到报错几乎不可能关键在于快速定位根因。下面是我实际碰到过并且有明确解决路径的四类问题你可以直接对照排查。5.1 cl.exe找不到或VS检测失败报错形态configure阶段出现“No supported C compiler found”或者显式提示无法定位Visual Studio。这个问题的原因通常是VS没有安装C工具链或者configure没找到VS安装位置。解决路径分三步。第一确认Visual Studio Installer里确实勾选了“使用C的桌面开发”。第二确认Windows 11 SDK已安装如果没有去VS Installer里修改组件。第三如果前两步都对在MSYS2 shell里手动导出VSINSTALLDIR环境变量指到VS的安装根目录然后删除旧configure缓存重新执行rm -rf build bash configure ...这里有一个常见的低级错误修改环境变量后忘记删除build目录旧配置里残留的检测结果会让configure误以为工具链仍然不可用。删掉build目录从零开始最稳。5.2 freetype的常见报错报错形态configure输出类似“Could not find freetype library”或者“The freetype test result is not usable”。根因基本只有一个没有提供符合MSVC ABI的freetype。网上很多教程说装MinGW的freetype包这是不行的。因为MSVC和MinGW的ABI在结构体对齐、符号命名上都有差异你用MinGW编译的freetype库去链接MSVC编译的JVMstatic库和动态库都会出幺蛾子。我推荐的解决方案是configure时用--with-freetype-src指向freetype源码目录。你只需要从官方下载对应版本的源码包解压到某个短路径下构建系统会自动把它编译成适用当前工具链的库。如果你确实不需要图形界面比如纯粹做一个服务端JVM实验也可以加--enable-headless-only跳过freetype检测。但我不建议常规操作这么做因为你后面跑AWT/Swing相关代码时会发现缺了字体渲染能力还得回过头来补。5.3 内存不足、编译器堆空间用尽报错形态make阶段出现fatal error C1060: compiler is out of heap space或者进程被系统直接kill。根因是并行编译太多MSVC的cl.exe进程同时吃满了物理内存。解决路径很简单降低jobs数量。我有个习惯内存16GB时jobs不超过88GB时jobs不超过4。如果已经在编译中途爆掉清理build目录重来或者至少把导致失败的中间目录删掉然后使用更保守的并行度。另外一个容易被忽视的点Windows的虚拟内存页面文件如果太小也会加剧这个问题。在“系统属性-高级-性能设置-高级-虚拟内存”里让系统自动管理页面文件大小通常就够了但如果你严格设置了固定大小建议改成4GB以上。5.4 杀毒软件导致的构建缓慢或偶发失败这个问题表面上不像错误更像是玄学编译进度一动不动或同一个错误反复出现换一台机器又完全正常。如果你用的是Windows自带的Defender大概率是实时扫描在捣乱。排查方法是打开任务管理器选择“性能-打开资源监视器”看磁盘活动里MsMpEng.exe进程是否长期占用很高。如果是把源码目录、build目录加入Defender排除列表。这一步不仅解决卡顿还能让构建时间显著缩短。另外如果你用了第三方安全软件构建时暂时退出监控等构建完成再开启。安全策略上请自己权衡不过就我体验来看把构建目录加入白名单是相对稳妥的做法。6. 验证产物并跑通自定义JVM构建成功不等于一切结束。拿到一份自己编译的JDK之后怎么验证它可用、怎么进行后续的定制迭代才是决定这项工作有没有价值的关键。6.1 验证JDK是否可用最基本的验证是把build目录下的JDK镜像加入PATH然后运行java -version和javac -version。但光这样不够我建议再做两件事。第一用一个稍微复杂的Java程序验证运行时稳定性比如启动虚拟线程、跑一段垃圾回收密集的任务。OpenJDK 21的新功能里虚拟线程和分代ZGC是两个重点随便写个并发任务压一下能暗示JVM内存管理和调度模块有没有问题。第二用javap -verbose检查某个编译出来的类文件版本。例如./bin/javap -verbose java.lang.String | head -30确认class file major version是65对应Java 21。这一步能帮你确认模块编译链路的完整性。如果你后续要调试JVM native代码建议在configure时把构建级别设为slowdebug。操作方式是在configure时加--with-debug-levelslowdebug这样生成的JDK包含完整的调试符号但性能和镜像大小都不适合日常使用。我通常的做法是维护两套构建一台机器或一个目录专门做release构建用于日常实验另一个目录做slowdebug构建用于调试。6.2 修改一处JVM源码后的重新编译流程当你能编译JDK并且能跑起来之后真正的“玩具时刻”开始了。比如你想在hotspot源码里加一段日志输出观察某个GC事件触发的时机流程是这样的先定位源码文件修改后用增量构建。绝大多数情况下只需要再次执行make images构建系统会根据文件时间戳判断哪些文件需要重新编译hotspot的C文件被改动后它会把受影响的目标文件重编一遍再重新链接JVM动态库整个过程通常只要几分钟到十几分钟远快于全量构建。这里有两个经验。第一修改C代码后如果新增了非静态全局变量要注意符号导出问题最好保持改动尽量局部化避免牵扯到跨平台的接口定义。第二如果你改了Java标准库代码构建系统会重新编译对应模块生成新的class文件验证时务必用build/windows-x86_64-server-release/images/jdk里的完整镜像而不是只替换某个jmod文件否则会踩到运行时模块校验的坑。我一开始做自定义修改的时候有个坏习惯源码改了十处觉得没必要每次重新构建攒到晚上再一起编译。结果一系列小改动彼此影响排错排到怀疑人生。后来学乖了每次只改一个点验证通过再做下一次改动。增量构建这么便宜没必要攒。还有一点修改源码时最好在源码仓库里创建自己的分支并给tag留好记录。虽然个人实验不需要复杂管理但至少每次改动前用git diff看一眼自己到底改了哪些文件。我之前有次改乱了hotspot的某个文件想恢复却记不清原始内容最后只能重新拉源码白白浪费了重新配置和编译的时间。Windows 11下编译OpenJDK 21说难其实不难说简单也确实劝退了不少人。整个流程最大的障碍不是某个技术点有多深而是它横跨了Windows环境下的多个子系统MSYS2的Unix模拟、MSVC的Windows原生工具链、autoconf的脚本逻辑、庞大的C源码树。任何一个环节的文档都算充分但链接成一条完成链路时细节和坑才是真正的门槛。我个人在实际操作中的体会是别急着追求全自动构建第一次就老老实实按手动步骤来搞清楚每一行configure参数在做什么。等你完整走通一遍之后再考虑写自动化脚本。脚本能帮你节省时间却没法帮你理解问题而对编译OpenJDK这件事来说理解问题本身才是最有价值的部分。最后再分享一个小技巧编译成功后把整个build目录压缩保存一份。之后无论是做增量实验还是对比调试你都有个干净且确定可用的基线这个习惯后来帮我省了不止一个周末。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑