简介:JDK17-Linux-Arm64 是面向 Linux 操作系统 Arm64 架构的 Java 开发工具包发行版,适合在 64 位 ARM 服务器、嵌入式设备及 Apple M1 等平台上构建与运行 Java 应用的开发者使用。该版本集成密封类、记录类、instanceof 模式匹配、增强的 switch 表达式以及 ZGC、Shenandoah 等低暂停垃圾收集器,并包含多项安全补丁,可满足现代化 Java 开发对类型安全、代码简洁性与运行性能的需求。压缩包共 384 个文件,以 jmod 模块文件、so 本地库、license 与 copyright 声明、md 说明文档及 policy、properties 配置为主,另含 java、javac、jshell、jpackage 等命令行工具,整体约 171.64MB,目录结构完整,解压配置 JAVA_HOME 与 PATH 后即可使用。目前已有 1389 人学习下载,适合需要搭建 Arm64 Java 开发环境、验证新语言特性或进行服务端部署的开发者参考使用。
1. JDK17-Linux-Arm64:一份能直接落地的国产化环境运行时包
第一次在 ARM64 服务器上跑 Java 服务,很多人会卡在一个很朴素的问题上:java -version报cannot execute binary file。不是代码写错了,也不是依赖没装,而是拿了一个 x86_64 的 JDK 往 ARM 机器上怼。这份 JDK17-Linux-Arm64 资源包解决的正是这个场景——它是面向 Linux + ARM64 架构的 JDK 17 运行时与开发工具集,包含java、javac、jlink、jpackage等完整组件,适合在国产化服务器、ARM 云主机、边缘计算盒子、树莓派类开发板上部署 Java 应用,也适合做跨架构构建和容器镜像的从业者。JDK 17 是长期支持版本,虚拟线程的前身、密封类、模式匹配、强封装 JDK 内部 API 这些特性都在这条线上稳定下来,选它做基线不是跟风,是省掉后面反复升级的麻烦。下面按「先确认包能不能用、再配环境、再跑构建、最后排坑」的顺序拆开讲。
2. 先验包再动手:架构、版本与目录结构核对
拿到一个 JDK 压缩包,最忌讳的是解压完直接export PATH然后祈祷。ARM64 环境里翻车的概率比 x86 高,因为很多默认脚本、容器基础镜像、CI runner 都还停留在 amd64 思维。这一章先把「这个包到底是什么、能不能在这台机器上用」确认清楚,再谈配置。
2.1 用 uname 和 file 双重确认架构
第一步不是解压,是看机器。uname -m给出内核报告的机器硬件名,ARM64 上通常是aarch64;file则直接读二进制文件的 ELF 头,告诉你这个可执行文件是给哪种架构编译的。两个命令交叉验证,能挡掉大部分「下错包」的低级错误。
# 确认当前机器架构,aarch64 即 ARM64 uname -m # 解压后进入 bin 目录,检查 java 二进制的目标架构 tar -xzf jdk17-linux-arm64.tar.gz cd jdk17-linux-arm64/bin file ./javauname -m输出aarch64说明机器是 64 位 ARM;如果输出x86_64,这个包就不该往这台机器上放。file ./java的正常输出里会带ELF 64-bit LSB executable, ARM aarch64字样,看到x86-64就说明包拿错了。这里有个容易忽略的点:部分 ARM 服务器在虚拟化层会把uname -m报成armv8l之类的变体,只要前缀是aarch64或arm64都算对路,但armv7l是 32 位 ARM,跑不了这个 64 位包。
2.2 版本号、构建号与发行信息怎么读
确认架构之后,看版本。JDK 的版本字符串不是随便一串数字,17.0.x+y里17是特性版本,0.x是更新版本,+y是构建号。更新版本决定你拿到的是不是带安全补丁的构建,构建号用于区分同一更新版本下的不同打包批次。资源包里的release文件是权威来源,比java -version更全。
# 查看 release 文件里的关键字段 cat ../release | grep -E 'JAVA_VERSION|IMPLEMENTOR|OS_ARCH|SOURCE' # 直接跑版本命令,确认可执行 ./java -version 2>&1release文件里的OS_ARCH="aarch64"是打包时写入的目标架构,和file的结果应当一致;JAVA_VERSION给出完整版本号;IMPLEMENTOR标识构建方。java -version的输出会打到标准错误,所以用2>&1合并,否则在某些脚本里会看不到输出。如果java -version直接报cannot execute binary file,回到 2.1 重新核对架构,别在版本号上纠缠。
2.3 目录结构里哪些目录必须保留
解压出来的 JDK 目录不是随便删的。bin放可执行文件,lib放核心类库和modules文件,conf放security、net.properties这类配置,include是给 JNI 编译用的头文件,jmods是jlink做裁剪镜像时的模块源。很多人为了省空间删掉jmods和include,结果后面用jlink打精简运行时或者编译带 JNI 的本地库时直接失败。
| 目录 | 作用 | 能否删 |
|---|---|---|
| bin | java、javac、jlink 等可执行文件 | 不能 |
| lib | 核心类库、modules、字体等资源 | 不能 |
| conf | 安全策略、网络、日志配置 | 不能 |
| jmods | jlink 裁剪镜像的模块源 | 做精简镜像时不能删 |
| include | JNI 头文件 | 有本地库编译需求时不能删 |
| legal | 许可证文本 | 可删,但建议保留 |
提示:如果只是跑现成 jar 包,
jmods和include确实用不上,但建议保留原包,别在解压目录上做破坏性删减,需要瘦身时用jlink生成独立运行时,而不是手删目录。
3. 环境变量与多版本共存:把 JAVA_HOME 配稳
架构和版本都确认了,接下来是配置。这一步看着简单,实际是 ARM64 环境里问题最集中的地方:JAVA_HOME指错、PATH顺序被系统自带 JDK 抢占、alternatives机制和手动配置打架。配稳的核心原则是——显式指定、顺序可控、可回退。
3.1 JAVA_HOME 与 PATH 的正确写法
JAVA_HOME指向 JDK 根目录,不是bin目录,这是最常见的错。PATH里要把$JAVA_HOME/bin放在系统路径前面,否则/usr/bin/java会先被命中。写进~/.bashrc或/etc/profile.d/下都行,生产环境建议用后者,避免不同用户环境不一致。
# 写入 /etc/profile.d/jdk17.sh,对所有登录用户生效 cat > /etc/profile.d/jdk17.sh <<'EOF' export JAVA_HOME=/opt/jdk17-linux-arm64 export PATH=$JAVA_HOME/bin:$PATH EOF # 立即生效并验证 source /etc/profile.d/jdk17.sh which java echo $JAVA_HOMEwhich java的输出必须是$JAVA_HOME/bin/java,如果还是/usr/bin/java,说明PATH顺序不对或者有别的 profile 脚本在后面覆盖。echo $JAVA_HOME确认变量已导出。这里用 heredoc 写入而不是echo >>,是为了避免多次 source 时重复追加。生产机上我一般还会在脚本末尾加一句hash -r,清掉 shell 的命令路径缓存,否则改完PATH当前会话可能还指着旧路径。
3.2 用 alternatives 管理多 JDK 的取舍
系统里已经装了别的 JDK 时,update-alternatives是另一条路。它把java、javac等命令做成符号链接,通过优先级切换。好处是系统级统一、切换方便;坏处是它只管命令链接,不管JAVA_HOME,很多构建工具(Maven、Gradle)读的是JAVA_HOME,光切 alternatives 不够。
# 注册两个 JDK 到 alternatives,优先级数字大的优先 update-alternatives --install /usr/bin/java java /opt/jdk17-linux-arm64/bin/java 1710 update-alternatives --install /usr/bin/javac javac /opt/jdk17-linux-arm64/bin/javac 1710 # 交互式切换 update-alternatives --config java优先级数字只是排序依据,不是版本号,我习惯用「主版本×100+更新号」的规则,比如 17.0.10 记作 1710,方便一眼看出对应版本。切换后仍要单独确认JAVA_HOME,两者不一致时以JAVA_HOME为准去排查构建工具的行为。容器环境里不建议用 alternatives,镜像层多、符号链接容易在跨阶段复制时断掉,直接在 Dockerfile 里设ENV JAVA_HOME更干净。
3.3 容器与 CI 里的环境注入
容器里配 JDK 和物理机思路不同:不要依赖登录 shell 的 profile 脚本,因为docker run默认不加载它们。正确做法是在 Dockerfile 里用ENV固化,或者docker run -e显式传入。CI runner 上则要注意 runner 进程本身的JAVA_HOME和 job 脚本里的可能不是一回事。
# Dockerfile 片段:基于 ARM64 基础镜像注入 JDK FROM arm64v8/debian:bookworm-slim COPY jdk17-linux-arm64 /opt/jdk17 ENV JAVA_HOME=/opt/jdk17 ENV PATH=$JAVA_HOME/bin:$PATH RUN java -versionFROM必须选 ARM64 架构的基础镜像,arm64v8/前缀是常见约定,具体以你用的镜像仓库标注为准。ENV在构建阶段和运行阶段都生效,RUN java -version放在构建期做一次自检,镜像构建失败比运行期失败好排查得多。CI 里如果 runner 是 ARM64 但 job 跑在容器里,记得容器内也要有这套ENV,别指望宿主机的JAVA_HOME穿透进去。
4. 编译与运行验证:从 HelloWorld 到 jlink 精简镜像
环境配好不等于能用。ARM64 上真正要验证的是三件事:javac能编译、java能跑、jlink能裁出可独立分发的运行时。这三步覆盖了开发、运行、交付三个环节,任何一步失败都说明包或配置有问题。这一章用最小可复现的例子把链路走通。
4.1 javac 编译与 java 运行的完整闭环
先写一个带一点现代语法的类,确认 JDK 17 的特性可用。用record和switch模式匹配,这两个在 17 上都是正式特性,能顺带验证编译器不是旧版本冒充的。
// Hello.java public class Hello { record Point(int x, int y) {} public static void main(String[] args) { Object o = new Point(3, 4); String desc = switch (o) { case Point p -> "point(" + p.x() + "," + p.y() + ")"; default -> "unknown"; }; System.out.println(desc); System.out.println(System.getProperty("os.arch")); } }# 编译并运行 javac Hello.java java Hellorecord是 JDK 16 转正的,switch模式匹配在 17 上是预览特性,编译时可能需要加--enable-preview,具体以你手上构建的版本行为为准;如果编译报预览特性错误,把switch换成普通if即可,不影响验证目的。System.getProperty("os.arch")在 ARM64 上应输出aarch64,这是运行时层面的架构自证,比uname更贴近 JVM 实际认知。编译产物是.class,字节码本身跨架构,所以同一个 jar 在 x86 和 ARM 上都能跑,前提是 JVM 架构对。
4.2 jlink 裁剪运行时的参数与坑
jlink是 JDK 9 之后交付环节的利器,能把整个 JDK 裁成只含所需模块的运行时,体积从两三百兆降到几十兆。ARM64 上做精简镜像尤其有价值,因为很多 ARM 设备的存储和带宽都紧张。核心参数是--add-modules指定模块、--output指定输出目录、--strip-debug去掉调试信息。
# 生成只含 java.base 和 java.logging 的精简运行时 jlink \ --add-modules java.base,java.logging \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress=2 \ --output /opt/jre17-mini # 验证精简运行时可用 /opt/jre17-mini/bin/java -version--add-modules里没列的模块在精简运行时里不存在,运行时报NoClassDefFoundError或module not found就是漏了模块,用jdeps分析依赖再补。--compress=2是压缩级别,2 比 1 压得更狠但启动稍慢,交付镜像用 2,本地调试用 1 或不加。--no-man-pages和--no-header-files去掉手册和头文件,纯运行场景够用。注意jlink生成的运行时和原 JDK 架构绑定,ARM64 上裁出来的只能在 ARM64 上用,别指望拿到 x86 机器上跑。
4.3 用 jdeps 反查模块依赖
裁剪之前得知道应用到底依赖哪些模块。jdeps能扫描 jar 或 class 文件,列出用到的 JDK 模块。手写--add-modules容易漏,用工具反查更稳。
# 分析 jar 的模块依赖,输出到文件便于比对 jdeps --multi-release 17 --print-module-deps app.jar > deps.txt cat deps.txt--multi-release 17告诉jdeps按 JDK 17 的多版本 jar 规则解析,--print-module-deps输出逗号分隔的模块列表,可以直接喂给jlink --add-modules。输出里如果出现not found,说明有第三方依赖没在 classpath 上,先把依赖补全再分析。这个命令在 ARM64 和 x86 上结果一致,因为分析的是字节码,不涉及本地执行,可以放心在任意架构上跑。
5. 避坑与排查:ARM64 上最容易翻车的五件事
前面把正常路径走通了,这一章专门收异常。ARM64 环境的坑有共性:架构不匹配、依赖链里混进 x86 本地库、容器基础镜像选错、字体和时区这类「非代码」问题。每条按现象、原因、解决写,照着对号入座。
5.1 现象:cannot execute binary file
原因几乎总是架构不匹配——x86_64 的 JDK 被放到 ARM64 机器上,或者反过来。少数情况是文件权限丢了执行位。解决:先用file确认二进制架构,和uname -m对齐;权限问题用chmod +x补上。如果是从压缩包解压的,注意某些打包方式会丢执行位,解压后统一chmod -R +x bin/一次。
5.2 现象:UnsatisfiedLinkError 指向某个 .so
原因是有 JNI 本地库只提供了 x86_64 版本,JVM 在 ARM64 上加载失败。典型的是某些数据库驱动、图像处理库、加密库。解决:找对应库的 ARM64 构建,或者换纯 Java 实现。排查时看异常里的.so路径,用file确认它的架构,和 JVM 架构比对。构建阶段可以用ldd检查动态链接依赖是否齐全。
5.3 现象:容器里 java 命令找不到或版本不对
原因是基础镜像架构不对,或者ENV没设、被后续层覆盖。多阶段构建时,COPY --from跨阶段复制可能把符号链接复制断。解决:docker inspect看镜像架构,docker run --rm image java -version直接验证;Dockerfile 里用绝对路径的JAVA_HOME,避免依赖PATH继承;跨阶段复制用COPY --from=builder /opt/jdk17 /opt/jdk17复制实体目录而不是链接。
5.4 现象:中文乱码或时区不对
原因不是 JDK 的问题,是运行环境缺字体或缺时区数据。ARM64 的精简基础镜像经常不带fontconfig和tzdata。解决:镜像里装fontconfig、tzdata,启动参数加-Duser.timezone=Asia/Shanghai和-Dfile.encoding=UTF-8。JDK 17 默认字符集已经是 UTF-8,但显式指定能避免被环境变量带偏。
5.5 现象:jlink 出来的运行时启动报模块缺失
原因是--add-modules漏了模块,或者应用用了反射加载的类,jdeps静态分析看不到。解决:先用jdeps拿基础列表,再在测试环境跑一遍完整业务流程,报错时按异常里的模块名补;反射场景可以在--add-modules里加ALL-MODULE-PATH做兜底,但那样就失去裁剪意义了,更好的办法是把反射用到的模块显式列出。
注意:ARM64 上排查问题时,先确认「是架构问题还是逻辑问题」。架构问题的报错通常出现在启动瞬间(二进制格式、链接库),逻辑问题出现在运行中。按这个时间点分流,能省掉大量无效排查。
6. 进阶:把 JDK17 做成可复现的交付基线
走到这里,包能用、环境能配、坑能排。剩下的是把它变成团队里可复现的东西,而不是每个人机器上一份手工配置。我的做法是三条:版本锁定、校验和固化、精简运行时随应用一起交付。版本锁定指在构建脚本里写死 JDK 的完整版本号和构建号,不用latest这类浮动标签;校验和固化指下载或分发时比对 SHA256,防止包在传输中被替换或损坏;精简运行时随应用交付指用jlink裁出运行时,和应用 jar 一起打进交付物,目标机器上不需要预装 JDK。
# 生成并校验 SHA256,固化到交付清单 sha256sum jdk17-linux-arm64.tar.gz > jdk17.sha256 sha256sum -c jdk17.sha256 # 交付目录结构:应用 jar + 精简运行时 + 启动脚本 # app/ # app.jar # runtime/ <- jlink 输出 # start.shsha256sum -c返回OK才算校验通过,交付清单里把哈希值和版本号一起记录,回滚时有据可查。启动脚本里用相对路径引用runtime/bin/java,避免依赖系统PATH。这套结构在 ARM64 设备上尤其省心,因为目标机器往往没有包管理器权限,预装 JDK 不现实。
还有一个容易被低估的点:跨架构构建。如果你的开发机是 x86,目标机是 ARM64,别在开发机上直接跑 ARM64 的 JDK 做验证,用容器模拟目标架构,或者把构建和验证都放到 ARM64 的 CI runner 上。我吃过一次亏——本地 x86 上jlink裁出来的运行时看着没问题,推到 ARM64 设备上启动就报链接错误,原因是裁剪时混进了宿主机的本地库。从那以后我每次交付 ARM64 产物,都强制在目标架构环境里跑一遍java -version加一个最小业务冒烟,确认无误才发版。希望帮到你。
本文还有配套的精品资源,点击获取