简介:JDK17-Linux-Arm64 是面向 Linux 操作系统 Arm64 架构的 Java 开发工具包发行版,适合在 64 位 ARM 服务器、嵌入式设备及 Apple M1 等平台上构建与运行 Java 应用的开发者使用。该版本集成密封类、记录类、instanceof 模式匹配、增强的 switch 表达式、低暂停 ZGC 与 Shenandoah 垃圾收集器以及多项安全补丁,可帮助开发者编写更简洁、类型更安全的代码并优化大规模应用性能。压缩包共 384 个文件,约 171.64MB,以 jmod 模块、so 本地库、license 与 copyright 声明、md 说明文档及 policy、properties 配置为主,另含 java、javac、jshell、jpackage 等命令行工具与 jsa、classlist 等运行时数据,目录结构完整,解压后配置 JAVA_HOME 与 PATH 即可使用。目前已有 1389 人学习下载,适合需要 Arm64 原生 JDK 环境进行开发、部署或迁移验证的读者参考取用。
1. JDK17-Linux-Arm64:一份能直接落地的国产化环境运行时包
第一次在 ARM64 服务器上装 JDK17,很多人会下意识去官网翻 x64 的 tar.gz,下载、解压、配环境变量,java -version一跑,报cannot execute binary file: Exec format error。这不是配置问题,是架构不对。这份 JDK17-Linux-Arm64 资源包解决的就是这件事:它是一份面向 Linux ARM64(aarch64)平台的 JDK17 运行时与开发工具集,解压即用,不需要在目标机上再编译,也不依赖发行版自带的包管理器。适合谁?一类是在鲲鹏、飞腾这类 ARM 服务器上做信创适配的后端工程师,一类是在本地用 ARM 开发板或 Apple Silicon 虚拟机跑 Linux 做验证的同学,还有一类是打包 Docker 镜像时需要把 JDK 塞进 arm64 基础镜像的运维。它不解决业务代码兼容性,但能把「运行时跑不起来」这个前置障碍先清掉。下面按「先确认架构、再解压配置、然后验证与排错」的顺序拆,参数和坑都写清楚。
2. 先确认架构与包结构:别在 x64 上白忙一场
拿到任何 JDK 压缩包,第一件事不是解压,是确认目标机架构和包内二进制是否匹配。ARM64 在 Linux 上有几个容易混的叫法:aarch64、arm64、armv8,内核和工具链里经常混用,但 JDK 的二进制只认 ELF 头里的 machine 字段。这一章先把「怎么判断、包里有什么、为什么选 tar.gz 而不是 deb/rpm」讲透,再动手。
2.1 用 uname 和 file 双重确认架构
很多人只看uname -m就下结论,这在容器里会翻车——容器共享宿主内核,uname -m返回的是宿主架构,不一定是镜像里二进制能跑的架构。稳妥做法是两步:先看内核架构,再用file看一个已知二进制的 ELF 头。
# 第一步:看内核架构,aarch64 或 arm64 都表示 ARM64 uname -m # 第二步:看系统里任意一个可执行文件的 ELF 头 # 输出里出现 aarch64 才是真正的 ARM64 用户态 file /bin/ls # 第三步:如果系统里已经有 java,看它是哪种架构 # 注意:这一步只作参考,旧 JDK 可能是 x64 转译层跑的 file $(readlink -f $(which java)) 2>/dev/null逻辑说明:uname -m读的是uname系统调用返回的 machine 字段,在 ARM64 上通常是aarch64;file读的是 ELF 文件头第 18 字节起的e_machine,ARM64 对应值 183(0xB7),显示为ARM aarch64。两者一致才能确认用户态也是 ARM64。参数上没什么可调的,关键是别只信一个命令。如果uname -m是aarch64但file /bin/ls显示x86-64,说明你在一个 x64 用户态、ARM 内核的混合环境里(常见于某些模拟层),这时候装 ARM64 的 JDK 是跑不起来的。
2.2 包内目录结构与该留该删
解压后典型结构是jdk-17/bin、jdk-17/lib、jdk-17/conf、jdk-17/jmods、jdk-17/include。对一线部署来说,几个目录的取舍值得说清楚:
| 目录/文件 | 作用 | 生产镜像是否保留 |
|---|---|---|
bin/java | 启动器 | 必留 |
bin/javac | 编译器 | 只跑不编译可删 |
lib/modules | 核心模块归档 | 必留 |
lib/src.zip | 源码包 | 可删,省 40MB 左右 |
jmods/ | 链接期模块 | 只跑不编译可删 |
include/ | JNI 头文件 | 无 native 编译可删 |
conf/ | 安全与网络配置 | 必留 |
常见做法是:开发机全留,容器镜像只留bin/java、lib/、conf/,jmods和src.zip删掉能省不少层体积。但要注意,如果业务用了jlink裁剪运行时,jmods是必须的,删了就没法再裁。我一般会在 Dockerfile 里分两个阶段,构建阶段用完整包,运行阶段只拷裁剪后的运行时。
2.3 为什么优先 tar.gz 而不是发行版包
ARM64 上的apt install openjdk-17-jdk或yum install java-17-openjdk看起来省事,但有两个现实问题:一是不同发行版仓库里的 JDK17 小版本差异大,有的还停在早期更新,安全补丁跟不上;二是包管理器装的 JDK 路径分散在/usr/lib/jvm下,符号链接层层嵌套,排查JAVA_HOME问题时很费劲。tar.gz 的好处是路径完全可控,解压到哪JAVA_HOME就是哪,迁移和回滚都是删目录的事。代价是要自己管更新,这个后面排错章节会讲怎么用脚本兜住。
3. 解压、环境变量与多版本共存
这一章是真正能抄作业的部分。目标是把包落到一个固定路径、配好JAVA_HOME和PATH、并且能和机器上已有的其他 JDK 共存不打架。顺序是:选路径 → 解压 → 配环境 → 验证 → 处理多版本。
3.1 固定路径解压与权限处理
# 约定安装根目录,避免散落在 /opt 和 /usr/local 两处 sudo mkdir -p /usr/local/jdk # 解压到临时目录再移动,避免解压中断留下半成品 tar -xzf jdk17-linux-arm64.tar.gz -C /tmp/ # 确认解压出来的顶层目录名(不同打包方式名字不一样) ls /tmp | grep -i jdk # 移动到固定路径,目录名统一成 jdk-17,方便脚本引用 sudo mv /tmp/jdk-17* /usr/local/jdk/jdk-17 # 修正属主,避免非 root 用户读不到 sudo chown -R root:root /usr/local/jdk/jdk-17 sudo chmod -R 755 /usr/local/jdk/jdk-17逻辑说明:先解压到/tmp再移动,是为了让「解压失败」和「安装失败」两件事分开,出问题时能判断是包损坏还是路径问题。mv时用通配符jdk-17*是因为不同来源的包顶层目录名可能是jdk-17、jdk-17.0.x等,统一改名成jdk-17后,后续脚本里JAVA_HOME就不用跟着版本号改。权限上755对目录和可执行文件都够,bin/java不需要 setuid,别乱加。如果目标机是多人共用,建议把 JDK 放在/usr/local/jdk而不是某个用户家目录,否则其他用户跑服务时会因为权限被拒。
3.2 JAVA_HOME 与 PATH 的正确写法
# 写入系统级 profile,对所有登录用户生效 sudo tee /etc/profile.d/jdk17.sh > /dev/null <<'EOF' export JAVA_HOME=/usr/local/jdk/jdk-17 export PATH=$JAVA_HOME/bin:$PATH EOF # 立即生效(当前 shell) source /etc/profile.d/jdk17.sh # 验证三个关键点 echo $JAVA_HOME which java java -version逻辑说明:JAVA_HOME指向 JDK 根目录,不是bin,这是最常见的写错点——写成.../jdk-17/bin后,Maven、Gradle、Tomcat 启动脚本拼出来的路径会变成.../bin/bin/java。PATH里把$JAVA_HOME/bin放最前面,是为了压过系统自带的/usr/bin/java。用/etc/profile.d/而不是直接改/etc/profile,好处是卸载时删一个文件就干净了。java -version输出里要确认三件事:版本号是 17.x、Server VM而不是Client VM、没有warning: Options -Xverify:none之类的异常提示。如果which java指向的还是/usr/bin/java,说明PATH顺序不对或者当前 shell 没重新加载。
3.3 多版本共存:alternatives 还是手动切
机器上已经有 JDK8 或 JDK11 时,直接改PATH会互相覆盖。两种做法:用update-alternatives托管,或者用脚本按需切换JAVA_HOME。前者适合系统级统一,后者适合一个用户跑多个项目。
# 方案一:alternatives 托管(Debian/Ubuntu 系) sudo update-alternatives --install /usr/bin/java java /usr/local/jdk/jdk-17/bin/java 1700 sudo update-alternatives --install /usr/bin/javac javac /usr/local/jdk/jdk-17/bin/javac 1700 # 查看并切换 sudo update-alternatives --config java # 方案二:按项目切换的 shell 函数,写进 ~/.bashrc jdk17() { export JAVA_HOME=/usr/local/jdk/jdk-17 export PATH=$JAVA_HOME/bin:$PATH java -version }逻辑说明:--install最后的数字是优先级,1700 只是习惯写法(对应 17),数值大的优先。--config会列出所有候选让交互选择,适合不常切的场景。方案二的函数适合「这个终端窗口跑 JDK17 项目、那个窗口跑 JDK8 老系统」的情况,缺点是只对当前 shell 生效,新开窗口要重新调。血泪经验是:别在~/.bashrc里无条件export JAVA_HOME,否则和系统级 profile 打架,排查时echo $JAVA_HOME出来的值和你以为的不一样,这种玄学问题能耗掉半天。
4. 验证运行时与容器化落地
配好环境只是第一步,真正要确认的是「这个 JDK 在你的业务场景下能不能跑」。这一章分两块:裸机上的运行时验证(含 JVM 参数在 ARM64 上的注意点),以及容器镜像里怎么把这份包塞进去。
4.1 裸机验证:从 hello 到 JVM 参数
# 最小验证:编译并运行一个类 cat > /tmp/Hello.java <<'EOF' public class Hello { public static void main(String[] args) { System.out.println("arch=" + System.getProperty("os.arch")); System.out.println("vm=" + System.getProperty("java.vm.name")); System.out.println("ver=" + System.getProperty("java.version")); } } EOF javac -d /tmp /tmp/Hello.java java -cp /tmp Hello # 看 JVM 实际识别的架构和内存 java -XshowSettings:system -version 2>&1 | head -20逻辑说明:os.arch在 ARM64 上应输出aarch64,如果输出amd64说明跑的是 x64 二进制(转译层),性能会打折。java.vm.name应是OpenJDK 64-Bit Server VM。-XshowSettings:system会打印 CPU 和内存信息,重点看Active Processor Count是否等于物理核数——ARM 服务器上如果这个值偏小,可能是 cgroup 限制或 JVM 的容器感知没生效。参数上,ARM64 的 JDK17 默认堆上限算法和 x64 一致(物理内存 1/4),但在大内存 ARM 机器上建议显式设-Xms和-Xmx,别依赖默认值,否则容器里容易 OOM。
4.2 容器镜像:多阶段构建与体积控制
# 构建阶段:用完整 JDK 编译 FROM arm64v8/debian:bookworm-slim AS builder COPY jdk17-linux-arm64.tar.gz /tmp/ RUN mkdir -p /opt/jdk && \ tar -xzf /tmp/jdk17-linux-arm64.tar.gz -C /opt/jdk --strip-components=1 ENV JAVA_HOME=/opt/jdk ENV PATH=$JAVA_HOME/bin:$PATH WORKDIR /build COPY . . RUN javac -d out $(find src -name '*.java') # 运行阶段:只留运行时必需 FROM arm64v8/debian:bookworm-slim COPY --from=builder /opt/jdk /opt/jdk COPY --from=builder /build/out /app ENV JAVA_HOME=/opt/jdk ENV PATH=$JAVA_HOME/bin:$PATH WORKDIR /app ENTRYPOINT ["java", "-Xms256m", "-Xmx512m", "-cp", "/app", "Hello"]逻辑说明:--strip-components=1是为了去掉 tar 包里的顶层目录,直接解到/opt/jdk,省掉后面再mv一步。基础镜像必须显式用 arm64 变体,否则在 ARM 构建机上拉到的可能是 x64 镜像,构建时exec format error。运行阶段只拷/opt/jdk,jmods和src.zip如果构建阶段没删会一起带过来,建议在 builder 里加一句rm -rf /opt/jdk/jmods /opt/jdk/lib/src.zip。-Xmx512m是示例值,实际按业务压测调,ARM 机器上堆外内存(DirectByteBuffer、Metaspace)的占用和 x64 差不多,别以为架构换了内存模型就变。
4.3 用 jlink 裁剪运行时(可选但值得)
# 只保留业务需要的模块,产出精简运行时 /usr/local/jdk/jdk-17/bin/jlink \ --add-modules java.base,java.logging,java.sql \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress=2 \ --output /opt/jre-min # 验证裁剪后的运行时 /opt/jre-min/bin/java -version du -sh /opt/jre-min逻辑说明:--add-modules列出业务实际用到的模块,漏了会在运行时抛NoClassDefFoundError,常见漏项是java.naming(JNDI)和java.desktop(某些日志/图形库间接依赖)。--compress=2是 zip 压缩级别,体积能再小一截,代价是启动时解压稍慢。--strip-debug去掉调试符号,生产环境够用。裁剪后的运行时通常 40~60MB,比完整 JDK 小一半以上,但注意它不能编译代码,只适合纯运行阶段。我一般会先用jdeps扫一遍依赖再定模块列表,别凭感觉列。
5. 避坑与排查:ARM64 上 JDK17 的五个高频翻车点
这一章按「现象 → 原因 → 解决」写,都是实际部署里反复出现的。遇到问题先对照这里,能省掉大量瞎试的时间。
5.1 现象:cannot execute binary file: Exec format error
原因:下载的包是 x64 版本,或者容器基础镜像是 x64 的,在 ARM64 上执行时内核发现 ELF 头 machine 字段不匹配。解决:用file bin/java确认是ARM aarch64;容器场景检查docker inspect里的Architecture字段是否为arm64;构建机如果是 x64,要用--platform linux/arm64或换 ARM 构建机。
5.2 现象:java -version正常,但业务启动报UnsatisfiedLinkError
原因:业务依赖了 native 库(如某些数据库驱动、压缩库、JNI 封装),这些.so是 x64 编译的,ARM64 上加载失败。解决:用ldd检查.so的架构,找 ARM64 版本的依赖,或者换纯 Java 实现。这类问题在信创迁移里最常见,光换 JDK 不够,整条依赖链都要过一遍。
5.3 现象:容器里Active Processor Count远小于实际核数
原因:JDK17 虽然支持容器感知,但依赖 cgroup v2 和正确的--cpus设置,某些老内核或运行时配置下识别不准,导致 JVM 按错误的核数分配 GC 线程和 ForkJoinPool 大小。解决:显式加-XX:ActiveProcessorCount=N,或者升级容器运行时;排查时用-XshowSettings:system对比nproc输出。
5.4 现象:JAVA_HOME设了但 Maven 仍用旧 JDK
原因:mvn脚本优先读JAVA_HOME,但如果PATH里mvn来自某个自带 JDK 的目录,或者~/.mavenrc里硬编码了路径,就会覆盖。解决:mvn -version看它报告的 Java 路径,检查~/.mavenrc、/etc/mavenrc、MAVEN_OPTS三处;用which mvn确认脚本来源。
5.5 现象:解压后bin/java没有执行权限
原因:tar 包在传输过程中权限位丢失(比如经过某些文件系统或压缩工具二次打包),解压出来是644。解决:chmod +x bin/*补上;更稳妥的是解压后统一chmod -R 755,并在部署脚本里加一步权限校验,别等启动时才报Permission denied。
6. 版本管理与升级:把 JDK 更新做成可回滚的动作
JDK17 是 LTS,但小版本更新(17.0.x)会带安全补丁,生产环境不能一直用初始版本。这一章讲怎么在不破坏现有部署的前提下更新,以及怎么验证更新没引入回归。核心思路是「版本目录并存 + 符号链接切换 + 启动前自检」,这样回滚就是改一个链接的事。
先约定目录结构:/usr/local/jdk/下放多个版本目录,jdk-17作为符号链接指向当前生效版本。升级时解压新版本到新目录,验证通过后再切链接。
# 当前结构 # /usr/local/jdk/jdk-17 -> jdk-17.0.9 # /usr/local/jdk/jdk-17.0.9/ # /usr/local/jdk/jdk-17.0.10/ (新版本) # 1. 解压新版本到独立目录 sudo tar -xzf jdk17-linux-arm64-new.tar.gz -C /usr/local/jdk/ sudo mv /usr/local/jdk/jdk-17.0.10* /usr/local/jdk/jdk-17.0.10 # 2. 用新版本跑一遍自检,不切链接 /usr/local/jdk/jdk-17.0.10/bin/java -version /usr/local/jdk/jdk-17.0.10/bin/java -cp /tmp Hello # 3. 自检通过后原子切换符号链接 sudo ln -sfn /usr/local/jdk/jdk-17.0.10 /usr/local/jdk/jdk-17 # 4. 验证生效版本 readlink -f /usr/local/jdk/jdk-17 java -version # 5. 回滚(出问题时) sudo ln -sfn /usr/local/jdk/jdk-17.0.9 /usr/local/jdk/jdk-17逻辑说明:ln -sfn的-n很关键,没有它,当目标已是符号链接指向目录时,ln会在链接内部再建一个链接,路径越套越深。切换是原子的,正在运行的 JVM 不受影响(它已经加载了旧版本的库),新启动的进程才会用新版本,所以滚动重启服务即可。自检那一步别省,我见过新版本小更新后某个 GC 参数行为微调导致启动变慢的情况,先在单机验证比全量重启后再回滚成本低得多。
验证更新是否引入回归,除了java -version,还要看三样:一是java -XshowSettings:vm -version里的默认堆参数有没有变;二是业务的关键路径压测(至少跑一遍冒烟用例);三是 GC 日志格式,JDK17 统一用-Xlog:gc*,如果老启动脚本里还写着-Xloggc,新版本会告警甚至忽略,这个在升级时特别容易漏。我一般会在切换前把新旧版本的-XshowSettings:properties输出 diff 一遍,重点看java.vendor、java.vm.version、sun.jnu.encoding这几项,编码相关的变化在中文环境里最容易出问题。
从那以后我每次动 JDK 版本,都强制走一遍「新目录解压 → 独立自检 → 符号链接切换 → 冒烟验证」这四步,绝不直接在原目录覆盖解压。覆盖解压看着快,一旦新版本有问题,旧版本的文件已经被冲掉,回滚只能重新下载,停机时间翻倍。这套流程多花十分钟,换的是出问题时能一分钟切回去的底气。希望帮到你。
本文还有配套的精品资源,点击获取