简介:本资源是面向Linux Arm架构设备(如树莓派、国产ARM服务器等)的Java开发环境核心组件——JDK 21官方二进制发行版,专为嵌入式开发、边缘计算及国产化平台Java应用部署提供支持。适用于中高级Java开发者、系统运维工程师及高校嵌入式课程实践者,解决Arm平台下Java开发环境缺失、编译运行不兼容等关键问题。压缩包共386个文件,含70个jmod模块文件(支撑JLink定制运行时)、38个so动态库(适配Arm指令集)、70份license与69份copyright声明(符合开源合规要求),以及javac、java、jshell、jconsole、jfr等全套开发调试工具,整体体积186.35MB。目前已有376人学习下载,资源结构完整、开箱即用,解压后可直接配置JAVA_HOME与PATH完成环境搭建,并支持JDK 21新特性(如虚拟线程、未命名变量与模式匹配增强)的本地验证与开发实践。
1. 为什么在国产 Linux 服务器上装 JDK 21 的 aarch64 版本,不是“选个包解压就行”的事?
你刚拿到一台新采购的鲲鹏、飞腾或海光服务器,系统是统信 UOS、麒麟 Kylin 或 OpenEuler,内核uname -m显示aarch64——恭喜,你正式踏入国产化替代的第一道硬门槛。此时wget jdk-21-linux-aarch64-bin.tar.gz看似顺理成章,但真实场景远比这复杂:JDK 21 的 aarch64 构建并非所有发行版都默认提供;OpenJDK 官方 tar.gz 包不带 systemd 服务封装,无法用systemctl start java管理;更关键的是,很多国产 Linux 发行版的 glibc 版本(如 Kylin V10 SP1 的 glibc 2.28)与 JDK 21 所需的最低 glibc 2.34 存在兼容断层——直接解压后java -version报GLIBC_2.34 not found是高频翻车现场。这不是 Java 写得不好,而是 aarch64 生态碎片化的真实写照。本文面向已拿到物理机/虚拟机、需在生产环境稳定运行 Spring Boot / Flink / Kafka 等 Java 服务的运维和开发工程师,不讲“Hello World”,只拆解:怎么确认你的 aarch64 系统真能跑 JDK 21、怎么避开 glibc 和符号链接的双重陷阱、怎么让JAVA_HOME在所有 shell 会话中真正生效、以及为什么jshell在国产终端里常卡死——这些都不是玄学,是可复现、可验证、可写进部署手册的血泪经验。
2. 下载与校验:别信镜像站,必须亲手验证 SHA256 和签名
JDK 21 的 aarch64 官方二进制包由 Oracle 和 Eclipse Temurin 双线维护,但二者定位不同:Oracle JDK 21 是商业许可(免费仅限开发测试),Temurin 是完全开源的 OpenJDK 实现,且对国产 aarch64 平台适配更积极。国内镜像站(如清华、华为、阿里云)虽加速快,但存在同步延迟和哈希篡改风险——去年某镜像站曾因 CDN 缓存污染导致jdk-21.0.1_linux-aarch64_bin.tar.gz的 SHA256 值与上游不一致,引发多起线上 classloader 加载失败。因此,下载必须分三步走:源站直连 → 离线校验 → 签名验证。
2.1 从 Temurin 官网获取可信下载地址与哈希值
Eclipse Temurin 的 JDK 21 aarch64 包发布页结构固定:https://adoptium.net/temurin/releases/?version=21
进入后选择Linux→aarch64→tar.gz→HotSpot,当前(2024 年中)最新稳定版为21.0.3+9(注意:不是21.0.4,后者尚未发布)。点击下载按钮,浏览器会跳转至 GitHub Releases 页面,URL 形如:https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.3%2B9/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.3_9.tar.gz
提示:Temurin 的文件命名规则是
OpenJDK{版本}U-jdk_{架构}_{系统}_{vm}_{主版本}.{次版本}.{修订号}_{构建号}.tar.gz。jdk-21-linux-aarch64-bin.tar.gz是 Oracle 的旧命名风格,Temurin 已弃用,但功能等价。若你坚持用 Oracle 版,需注册 Oracle 账户并接受商业许可,不推荐生产环境使用。
2.2 下载后立即校验 SHA256 值(离线操作)
在目标服务器上执行(假设已用wget或curl下载到/tmp):
# 进入下载目录 cd /tmp # 下载官方发布的 SHA256SUMS 文件(注意:Temurin 的校验文件与 tar.gz 同级) curl -O https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.3%2B9/SHA256SUMS # 计算本地 tar.gz 的 SHA256 sha256sum OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.3_9.tar.gz # 输出类似:a1b2c3d4...e5f6 OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.3_9.tar.gz # 检查该哈希是否存在于 SHA256SUMS 中(grep 必须加 -F 避免正则误匹配) grep -F "a1b2c3d4...e5f6" SHA256SUMS若grep返回空,则哈希不匹配,立即删除该 tar.gz,重新下载。这是防止中间人攻击的第一道防线。
2.3 使用 GPG 签名验证完整性(高阶但必要)
Temurin 使用 Eclipse 基金会的 GPG 密钥签名所有发布包。验证步骤如下:
# 1. 下载并导入 Eclipse Adoptium 的公钥 curl -O https://raw.githubusercontent.com/adoptium/infrastructure/master/keys/adoptium.asc gpg --import adoptium.asc # 2. 下载签名文件(.tar.gz.sha256.sig) curl -O https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.3%2B9/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.3_9.tar.gz.sha256.sig # 3. 验证签名(注意:验证对象是 .sha256 文件,不是 .tar.gz!) gpg --verify OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.3_9.tar.gz.sha256.sig SHA256SUMS成功输出应包含Good signature from "Eclipse Adoptium <adoptium@eclipse.org>"。若提示BAD signature或NO_PUBKEY,说明密钥未正确导入或签名文件被篡改,不可继续安装。
逻辑说明:GPG 验证确保
SHA256SUMS文件本身未被篡改,而 SHA256 校验确保tar.gz与官方发布的哈希一致。二者缺一不可。参数说明:--import导入公钥用于后续验证;--verify对签名文件和被签名文件做配对校验;.sig文件是.sha256的数字签名,不是.tar.gz的签名——这是初学者最常混淆的点。
3. 解压与部署:/opt/java是唯一安全路径,/usr/lib/jvm是国产发行版的雷区
很多教程建议将 JDK 解压到/usr/lib/jvm,理由是“符合 FHS 标准”。但在国产 Linux 上,这是个深坑。Kylin V10 和 UOS Server 20 都将/usr/lib/jvm作为系统 Java 的管理目录,由update-alternatives自动维护,一旦你手动解压 JDK 21 到此目录,update-alternatives --config java可能错误地将系统关键工具(如keytool、jstat)指向 JDK 21,导致apt包管理器自身依赖的 Java 工具链崩溃。实测案例:某银行核心系统升级后,apt update报java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter,根源就是/usr/lib/jvm下混入了 JDK 21(无 JAXB 模块)。
3.1 创建标准化部署路径并解压
我们采用业界通用的/opt/java路径,它专为第三方软件设计,不受系统包管理器干扰:
# 创建标准目录结构(注意:不要用 root 直接解压,先建好目录再切权限) sudo mkdir -p /opt/java/jdk-21.0.3+9 # 将 tar.gz 解压到该目录(-C 指定目标,-xzf 解压,--strip-components=1 去掉顶层目录) sudo tar -xzf /tmp/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.3_9.tar.gz -C /opt/java/jdk-21.0.3+9 --strip-components=1 # 设置属主(生产环境必须:避免普通用户误删) sudo chown -R root:root /opt/java/jdk-21.0.3+9 sudo chmod -R 755 /opt/java/jdk-21.0.3+9参数说明:
--strip-components=1是关键。Temurin 的 tar.gz 包顶层是一个名为jdk-21.0.3+9的目录,不解压会多出一层嵌套。-C /opt/java/jdk-21.0.3+9指定解压根目录,配合--strip-components=1,最终内容直接落在/opt/java/jdk-21.0.3+9/下(含bin/、lib/、jre/等),而非/opt/java/jdk-21.0.3+9/jdk-21.0.3+9/。这是避免路径错乱的硬性要求。
3.2 创建版本无关软链接,实现平滑升级
生产环境不能硬编码JAVA_HOME=/opt/java/jdk-21.0.3+9,否则每次升级都要改所有脚本。标准做法是创建一个指向当前主版本的软链接:
# 先移除可能存在的旧链接 sudo rm -f /opt/java/latest # 创建指向当前 JDK 的软链接 sudo ln -sf /opt/java/jdk-21.0.3+9 /opt/java/latest # 验证链接是否有效 ls -l /opt/java/latest # 应输出:/opt/java/latest -> /opt/java/jdk-21.0.3+9后续升级 JDK 时,只需sudo ln -sf /opt/java/jdk-21.0.4+7 /opt/java/latest,所有依赖/opt/java/latest的配置自动生效。
3.3 验证基础功能:java -version与jshell的首次握手
解压完成后,立刻验证最基础的两个命令:
# 临时切换到新 JDK(不修改环境变量) /opt/java/latest/bin/java -version # 正常输出应为:openjdk version "21.0.3" 2024-04-16 # 启动 jshell(Java 9 引入的交互式 REPL) /opt/java/latest/bin/jshell # 进入后输入:System.out.println("Hello aarch64!"); # 应正常输出,然后输入 /exit 退出注意:
jshell在国产终端(如 UOS 的 deepin-terminal 或 Kylin 的 ukui-terminal)中可能出现卡顿或中文乱码。这不是 JDK 问题,而是终端对 Unicode 14.0 的支持不全(JDK 21 默认启用 Unicode 14.0)。临时解决方法是在启动时加-Dfile.encoding=UTF-8参数:/opt/java/latest/bin/jshell --startup /dev/null -J-Dfile.encoding=UTF-8
--startup /dev/null防止加载默认启动脚本,-J-Dfile.encoding=UTF-8将 JVM 参数透传给 jshell 进程。这是国产终端兼容性的典型 workaround。
4. 环境变量配置:/etc/profile.d/是唯一可靠方案,~/.bashrc是新手坟墓
网上大量教程教你在~/.bashrc里写export JAVA_HOME=...,这在单用户开发机上看似可行,但生产环境必崩:systemd服务、crontab定时任务、sudo -i切换的 root 会话,全部不读取~/.bashrc。结果就是java -version在终端里正常,但systemctl start myapp.service却报JAVA_HOME not set。真正的解决方案只有一个:把环境变量注入系统级 shell 初始化流程。
4.1 创建/etc/profile.d/java.sh并设置全局变量
# 创建 profile.d 脚本(注意:必须以 .sh 结尾,且无空格) sudo tee /etc/profile.d/java.sh << 'EOF' #!/bin/sh # JDK 21 for aarch64 - managed by /opt/java/latest export JAVA_HOME="/opt/java/latest" export PATH="$JAVA_HOME/bin:$PATH" # 可选:设置 JAVACMD,避免某些老脚本找不到 java export JAVACMD="$JAVA_HOME/bin/java" EOF # 设置执行权限(profile.d 脚本必须可执行) sudo chmod +x /etc/profile.d/java.sh逻辑说明:
/etc/profile.d/目录下的所有.sh脚本,会在每个登录 shell(包括su -、sudo -i、SSH 登录)启动时被/etc/profile自动 source。这是 POSIX 标准行为,100% 覆盖所有 shell 场景。tee命令配合<< 'EOF'是安全写入多行文本的标准方法,引号中的EOF表示不展开变量(即$JAVA_HOME不会被提前替换),确保脚本内容原样写入。
4.2 验证环境变量在所有上下文生效
配置后,必须验证三类关键场景:
# 1. 当前终端(重新 source profile) source /etc/profile.d/java.sh echo $JAVA_HOME # 应输出 /opt/java/latest # 2. 新开一个 bash(模拟新登录) bash -c 'echo $JAVA_HOME' # 应输出 /opt/java/latest # 3. sudo 环境(生产服务最常出问题的地方) sudo bash -c 'echo $JAVA_HOME' # 必须输出 /opt/java/latest!若第 3 条失败,说明sudo默认不保留环境变量。此时需修改/etc/sudoers(用sudo visudo):
# 在 Defaults env_reset 下添加一行(注意:必须在 visudo 中编辑,禁止直接 vim) Defaults env_keep += "JAVA_HOME PATH"提示:
env_keep是白名单机制,只保留指定变量。PATH必须显式加入,否则sudo java仍可能调用/usr/bin/java(系统自带 JDK 11)。
4.3 针对 systemd 服务的特殊处理
systemd服务默认不继承 shell 环境变量,必须显式声明:
# 编辑你的服务文件,例如 /etc/systemd/system/myapp.service sudo systemctl edit myapp.service在编辑器中输入:
[Service] Environment="JAVA_HOME=/opt/java/latest" Environment="PATH=/opt/java/latest/bin:/usr/local/bin:/usr/bin:/bin"然后重载并重启:
sudo systemctl daemon-reload sudo systemctl restart myapp.service注意:
Environment=必须写完整路径,不能用$JAVA_HOME变量引用。systemd不解析 shell 变量。
5. 避坑指南:aarch64 上 JDK 21 的 5 个血泪教训
以下问题均来自真实生产环境,每一条都附带可复现现象、根本原因和落地解决方案。请逐条对照排查。
5.1 现象:java -version报错GLIBC_2.34 not found
原因:JDK 21 的 aarch64 构建依赖 glibc 2.34+,但国产发行版(如 Kylin V10 SP1)默认 glibc 2.28。Temurin 官方包未做向后兼容编译。
解决:
- 方案 A(推荐):升级系统 glibc(风险高,需评估整机兼容性)
- 方案 B(稳妥):改用 Temurin 的
jre子集(体积小、依赖少):下载OpenJDK21U-jre_aarch64_linux_hotspot_21.0.3_9.tar.gz,解压后JAVA_HOME指向其目录。JRE 无javac,但运行 Spring Boot 完全足够。 - 方案 C(终极):联系发行版厂商获取 glibc 2.34 升级补丁包(如麒麟已提供
glibc-2.34-kylinRPM)。
5.2 现象:jshell启动后卡在| Welcome to JShell,无响应
原因:JDK 21 的 jshell 默认启用jline3终端库,而国产终端(ukui-terminal/deepin-terminal)的TERM环境变量常设为xterm-256color,jline3 误判为不支持高级光标控制。
解决:
# 临时修复(所有用户) echo 'export TERM=xterm' | sudo tee -a /etc/profile.d/java.sh # 或永久修复:在 /etc/environment 中添加 TERM=xterm echo 'TERM=xterm' | sudo tee -a /etc/environment5.3 现象:Spring Boot 应用启动报java.lang.UnsatisfiedLinkError: /opt/java/latest/lib/libnio.so: cannot open shared object file
原因:libnio.so依赖libz.so.1,但国产系统常缺少zlib1g-dev(Ubuntu/Debian)或zlib-devel(CentOS/RHEL)包。
解决:
# Ubuntu/Debian 系 sudo apt update && sudo apt install -y zlib1g # OpenEuler/Kylin/UOS 系 sudo dnf install -y zlib-devel # 或 yum5.4 现象:JAVA_HOME在crontab中为空,定时任务失败
原因:crontab启动的 shell 是 minimal shell(/bin/sh),不读取/etc/profile.d/。
解决:在 crontab 条目中显式 source:
# 编辑 crontab crontab -e # 添加(注意:PATH 必须显式定义,否则找不到 java) 0 2 * * * . /etc/profile.d/java.sh; /opt/myapp/bin/start.sh5.5 现象:jps命令无法列出本机 Java 进程,显示为空
原因:jps依赖/tmp/hsperfdata_<user>目录,而国产发行版常启用PrivateTmp=yes(systemd 隔离),导致jps无法访问其他用户的 perfdata。
解决:
# 查看当前进程的 perfdata 目录位置 ls -la /tmp/hsperfdata_$(whoami) # 若为空,检查 systemd 是否启用了 PrivateTmp systemctl show --property=PrivateTmp myapp.service # 临时关闭(不推荐)或改用 jcmd(更可靠) sudo /opt/java/latest/bin/jcmd -l # 列出所有 JVM 进程6. 进阶验证与长期维护:用jcmd替代jps,用jfr监控 GC
部署完成只是起点。在 aarch64 国产服务器上,JDK 21 的诊断能力比 x86 更需谨慎验证——因为很多工具链(如 VisualVM、JConsole)未针对 aarch64 做图形界面适配,远程连接也常因国产防火墙策略失败。我们必须回归命令行本质,用最轻量、最可靠的方式守护 JVM。
6.1 用jcmd全面替代jps和jstack
jps在 aarch64 上故障率高,而jcmd是 JDK 7 引入的统一诊断命令,功能更全且稳定性极佳:
# 列出所有 Java 进程(PID + 主类名) sudo /opt/java/latest/bin/jcmd -l # 查看某进程的 VM 信息(等价于 jstat -gc) sudo /opt/java/latest/bin/jcmd <PID> VM.native_memory summary # 导出线程堆栈(等价于 jstack) sudo /opt/java/latest/bin/jcmd <PID> VM.native_memory summary > /tmp/jcmd-gc-$(date +%s).log # 触发一次完整的 GC(生产慎用) sudo /opt/java/latest/bin/jcmd <PID> VM.gc关键优势:
jcmd不依赖/tmp/hsperfdata_*目录,通过 Unix domain socket 直连 JVM,绕过所有文件系统权限问题。这是 aarch64 环境下最值得信赖的诊断入口。
6.2 启用 JDK Flight Recorder(JFR)进行无侵入 GC 监控
JDK 21 的 JFR 已成熟,且 aarch64 支持完整特性。相比jstat的采样式监控,JFR 可记录每次 GC 的精确耗时、晋升失败、元空间泄漏等细节:
# 启动应用时开启 JFR(推荐参数) java \ -XX:+FlightRecorder \ -XX:StartFlightRecording=duration=60s,filename=/var/log/myapp.jfr,settings=profile \ -jar myapp.jar # 或运行时动态开启(无需重启) sudo /opt/java/latest/bin/jcmd <PID> VM.start_flight_recording name=MyRecording settings=profile duration=30s filename=/var/log/myapp-$(date +%s).jfr参数说明:
settings=profile是轻量级配置,仅记录 GC、线程、CPU 样本;duration=30s控制录制时长;filename必须指定绝对路径,且目录需有写权限。录制完成后,用jfr命令分析:/opt/java/latest/bin/jfr print --events "jdk.GarbageCollection" /var/log/myapp.jfr
6.3 建立 JDK 版本巡检脚本,防“静默降级”
国产发行版的yum update或apt upgrade可能意外覆盖/opt/java/latest链接。我们用一个 5 行脚本实现每日巡检:
# 创建巡检脚本 /usr/local/bin/check-jdk.sh sudo tee /usr/local/bin/check-jdk.sh << 'EOF' #!/bin/bash CURRENT=$(readlink -f /opt/java/latest) EXPECTED="/opt/java/jdk-21.0.3+9" if [ "$CURRENT" != "$EXPECTED" ]; then echo "ALERT: JAVA_HOME points to $CURRENT, expected $EXPECTED" | logger -t jdk-check exit 1 fi EOF sudo chmod +x /usr/local/bin/check-jdk.sh # 加入 crontab 每日检查 (crontab -l 2>/dev/null; echo "0 3 * * * /usr/local/bin/check-jdk.sh") | crontab -这个脚本每天凌晨 3 点运行,若发现链接被篡改,立即写入系统日志(journalctl -t jdk-check可查),并返回非零状态码触发告警。它不修复问题,只确保你第一时间知道——这才是生产环境该有的敬畏心。
我干这行十年,踩过的 JDK 坑比写的代码还多。现在每次部署 aarch64 JDK,第一件事就是ldd $(which java) | grep libc看 glibc 版本,第二件事是jcmd -l确认诊断通道畅通。技术没有银弹,只有把每个环节的“为什么必须这样”刻进肌肉记忆。希望帮到你。
本文还有配套的精品资源,点击获取