☰
ARM64服务器JDK 8安装部署与避坑指南
2026/10/20 9:20:12 网站建设 项目流程

简介:jdk-8u321-linux-aarch64.tar.gz 是面向 Linux AArch64(ARM64)服务器的 Java 开发工具包更新版,适合需要在国产化或 ARM 架构上部署 Java 应用的开发、运维人员。该压缩包提供完整 JDK 组件,包含 Java 编译器、运行时环境和 javac、java、jstat、jmap、jstack 等常用工具,解压配置后可支撑微服务、Hadoop 集群及分布式业务系统的基础环境搭建。

包体共 406 个文件,压缩后约 71.02MB,以 man 手册页、Markdown 说明文档、so 动态库、jar 工具包、ttf 字体、properties 配置文件为主,涵盖命令帮助、部署文档、运行库、工具模块与安全配置,目录结构清晰,便于快速定位所需组件。该资源已有 1672 人学习下载,在 AArch64 平台的 JDK 8 版本中关注度较高。

使用这套资料,可获得针对 JDK 8u321 的完整组件与可复用部署参考,包括环境变量配置、JVM 常见调优参数、性能监控工具用法以及容器化部署思路,帮助读者在 ARM64 服务器上快速交付稳定 Java 运行环境,并为后续运维排障和分布式集群管理打下基础。

1. 一个压缩包解决 ARM64 服务器的 JDK 8 刚需

如果你运维的服务器里悄悄多了几台 ARM 架构的机器,最先要处理的往往不是数据迁移,而是把 JDK 8 环境立起来。这个 jdk-8u321-linux-aarch64.tar.gz 就是给 Linux 服务器准备的 Java 8 运行时包,架构是 aarch64,格式是 tar.gz,拿到手不用编译,解压配置完就能跑。适合老项目迁移、分布式节点扩容,也适合新接手的机器需要立刻恢复服务的情况。

Java 号称跨平台,但 JRE 的二进制可没有跨架构一说。x86_64 机器上下载的 JDK 不能用在 ARM 机器上,强行运行会直接报 cannot execute binary file。我拆这个安装包的过程踩了不少坑,包括时区、TLS、内存参数,这篇笔记按部署顺序把选型、解压、环境变量、systemd 托管和避坑记录串起来。新手可以完整跟下来,熟手可以直接跳到第 4 章挑自己踩过的坑。都是服务器运维里的真实场景,不是把官网文档换个排版。

2. 为什么是 aarch64 + JDK 8:选型与架构边界

2.1 先确认服务器架构,别选错包

在所有操作之前,先确认机器架构。登录一台 Linux 服务器后跑uname -m,输出只有几行,但决定你后面所有软件包选型是否正确。

uname -m

输出x86_64表示 64 位 x86 架构,输出aarch64表示 64 位 ARM 架构,而这个安装包文件名里的 aarch64 就决定了它只能跑在后者上。还有一种输出是armv7l,那是 32 位 ARM 环境,同样不能安装这个 aarch64 包,只能另找 32 位 ARM 版本的 JDK。看输出是最直接的,我曾经遇到过有台机器系统是 64 位,但用户态被设置成 32 位,这类边界情况后面第 4 章会再讲。

下载完成后,我习惯再用file命令确认压缩包不是个空壳。

file jdk-8u321-linux-aarch64.tar.gz

正常输出里会出现gzip compressed data,这至少说明包完整,没有被当成文本文件下载。如果解压后再执行file bin/java,通常能看到ELF 64-bit LSB executable, ARM aarch64这样的信息。这个信息在排障时很有用,因为一台服务器上可能同时放了好几个不同架构的 JDK 目录,仅凭文件名容易记混,看文件头最可靠。

很多人会问:既然服务器有包管理器,为什么不用apt或yum直接装?常见做法是直接装,但包管理器给的通常是发行版默认的 JDK 版本,不会帮你锁死 8u321 这种具体维护版。尤其是线上有一套已经调好的 JVM 参数和启动脚本,最怕默认版本变更导致行为不一致。用 tar.gz 的好处是版本精确、目录隔离,多放几个互不影响。

2.2 为什么坚持选 8u321

很多老系统从设计之初就是按 JDK 8 编写的。升级到 JDK 11 或 17,意味着要处理模块系统拆分、废弃接口移除,还有大量第三方库的行为变化。在一个分布式集群里,与其让几十个节点同时冒险升级,不如先统一到一个确定版本。8u321 是 JDK 8 生命周期里比较晚的维护版,包含多轮安全更新,比最初那些 8u20 之类版本要稳得多。面对漏洞扫描和合规要求,旧版本很容易被标记,而 8u321 在 8 系列里算是对运维比较友好的一个落点。

至于为什么不直接用更高版本,我列一张表,方便你决定手上的项目值不值得迁移。

代际模块系统主要可用 GC适合场景
JDK 8无CMS、G1老项目迁移、稳定线上
JDK 11有G1、ZGC 实验需要局部升级的过渡期
JDK 17有G1、ZGC 正式新项目、长期运行

JDK 8 的 CMS 垃圾回收器在后续版本里逐渐被移除,但在 8u321 里仍然是可靠可用的。对于跑满三年的老业务,GC 参数已经调过很多轮,直接换 JDK 大版本等于把调优推倒重来。所以坚持 8u321 不是情怀,是成本考量。有一点要注意,JDK 8 本身已经不在开源社区的常规更新期内,厂商发布的这个 8u321 维护版是你还能拿到的安全补丁相对完整的版本。

2.3 tar.gz 和 rpm/deb 的选择差异

同样是那个版本,分发格式有 tar.gz、rpm、deb、exe 等。rpm 和 deb 适合交给系统包管理器统一管理,安装时会把文件散到系统的各个目录,卸载时执行脚本清理。这在单机环境没问题,但在服务器集群里会有两个麻烦:一是升级回滚需要依赖包管理器事务,二是同一台机器想装两个 JDK 8 版本时,rpm/deb 的路径冲突很头疼。

tar.gz 是把整个 JDK 目录完整释放到某个约定目录,比如/usr/local/java。做多版本共存时,只需要把新旧目录并列放好,然后靠环境变量或软链接切换。在分布式环境下,我用 tar.gz 的另一个原因是标准化:所有节点上的 JDK 目录结构完全一致,自动化脚本只要把压缩包分发到相同路径,解压完成后各节点的相对路径都相同,服务单元文件也就能够保持统一。

压缩包里顶层一般是一个类似jdk1.8.0_321的目录,里面是 bin、lib、jre 这些标准子目录。解压后没有复杂的注册步骤,也不需要 root 权限就能运行,只要当前用户对目录有读和执行权限。这个特点在跑非 root 服务的场景特别重要,后面写 systemd 时就会体现。

3. 安装部署完整过程:从 tar 包到多实例服务

3.1 校验文件完整性

下载完先不要急着解压。一个损坏的 tar.gz 会在解压到一半时报gzip: invalid compressed data,那时候再排查是网络丢包还是服务器存储问题,就浪费更多时间。我的做法是每次下载后先算哈希值。

sha256sum jdk-8u321-linux-aarch64.tar.gz

把输出的 64 位十六进制字符串,和发布页面提供的校验值逐位比对。就算发布页面一时找不到校验值,也可以再下载一个镜像的同一个文件,对两个哈希值是否相同。这一步看起来繁琐,但对公共运行时来说,能同时确认两件事:传输过程没损坏,以及拿到的不是被篡改过的文件。

确认无误后,我把压缩包统一放在/opt/installers,解压目标用/usr/local/java。

sudo mkdir -p /usr/local/java sudo tar -xzf jdk-8u321-linux-aarch64.tar.gz -C /usr/local/java ls -l /usr/local/java

解压后看到的是类似jdk1.8.0_321的目录。这里注意,如果之前已经有一个旧的同版本目录,我习惯先把旧目录改名再解压新的,而不是直接覆盖。

sudo mv /usr/local/java/jdk1.8.0_321 /usr/local/java/jdk1.8.0_321.bak

这样做的原因很实际:解压不需要原子性,但如果你已经配置了环境变量和 systemd 单元文件,指向的路径不能中途消失。先保留旧目录,等新目录验证完java -version正常,再删除备份,这是一个随时有后悔药的部署习惯。

3.2 配置环境变量与多版本共存

解压后第一件事是验证可执行文件:

/usr/local/java/jdk1.8.0_321/bin/java -version

能输出 Java 版本信息,说明这个包没有选错。接下来配置环境变量。我不建议临时用export,因为服务器重启后环境就没了。也不建议直接把路径写进/etc/profile,我更常用的是在/etc/profile.d/下单独建一个脚本,这样管理时一眼就能看到 Java 相关配置。

sudo tee /etc/profile.d/java.sh <<'EOF' export JAVA_HOME=/usr/local/java/jdk1.8.0_321 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar EOF source /etc/profile.d/java.sh echo $JAVA_HOME

CLASSPATH 这一段在老项目中经常需要,因为很多历史代码直接引用了dt.jar和tools.jar。不建议把 CLASSPATH 留空,除非你确认项目不需要。如果你在纯命令行下执行java报找不到命令,先检查source是否执行成功,再看/etc/profile.d/java.sh的权限是否所有用户可读。

多版本共存的场景,我会用update-alternatives这套机制来管理。先注册新的 JDK:

sudo update-alternatives --install /usr/bin/java java /usr/local/java/jdk1.8.0_321/bin/java 1 sudo update-alternatives --install /usr/bin/javac javac /usr/local/java/jdk1.8.0_321/bin/javac 1 sudo update-alternatives --config java

--config java会列出当前已注册的所有 JDK,输入对应序号就能切换默认版本。这个方法比手动改软链接安全,因为 alternatives 会自动处理中断切换。需要注意的是,update-alternatives在部分精简服务器镜像里没有预装,报错提示找不到命令时,先通过包管理器安装,这不属于 JDK 本身的问题。只用单一版本的话,则可以省略这一节,直接在 profile 里把路径写死。

3.3 用 systemd 托管分布式服务

有了 JDK 8,下一步就是把应用跑起来。生产环境我从不使用nohup java -jar &这种方式,因为无法控制进程重启策略和日志管理。我的做法是写一个 systemd 服务单元文件,让守护进程管理 Java 服务的生命周期。

假设应用是一个可执行 jar,放在/opt/app/app.jar,运行用户是appuser,服务单元文件可以这么写:

sudo tee /etc/systemd/system/app.service <<'EOF' [Unit] Description=Java Application Service After=network.target [Service] Type=simple User=appuser WorkingDirectory=/opt/app Environment=JAVA_HOME=/usr/local/java/jdk1.8.0_321 Environment=TZ=GMT+08:00 ExecStart=/usr/local/java/jdk1.8.0_321/bin/java -Xms512m -Xmx1024m -Duser.timezone=GMT+08:00 -jar /opt/app/app.jar SuccessExitStatus=143 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF

写完后需要重新加载服务定义并启动:

sudo systemctl daemon-reload sudo systemctl enable --now app systemctl status app -l

这段配置有几个关键点。User=appuser决定了进程权限,不要用 root 跑 Java。-Xms和-Xmx控制堆内存初始值和最大值,我这里给了 512m 到 1024m,具体数值要看应用实际负载。SuccessExitStatus=143是配合优雅停机配置,Java 进程收到 SIGTERM 时退出码是 143,把它标记为正常退出,systemd 就不会当成错误记录。Environment=TZ=GMT+08:00解决容器和镜像常见的时区偏差,后面第 4 章还会展开。

在分布式集群里,这个 unit 文件可以复制到所有节点。分发方式可以是配置管理工具,也可以手动逐台执行。重点是 JDK 目录路径、jar 路径、运行用户三个维度必须完全一致,否则在一个节点验证过的脚本到另一个节点就会翻车。服务启动失败时,第一时间看systemctl status app -l里的 CGroup 日志,以及journalctl -u app -f,大多数启动错误就藏在最后几行。

4. 服务器上常见 JDK 8 坑与排查记录

4.1 报错 cannot execute binary file: Exec format error

现象:解压完 jdk-8u321-linux-aarch64.tar.gz 后,运行bin/java -version,系统直接回复cannot execute binary file: Exec format error。

原因:这台服务器的 CPU 架构与压缩包不匹配。常见场景是下载了 aarch64 包,但目标机器是 x86_64。另一种场景是系统里实际运行的内核支持 ARM64,但用户态环境是 32 位,这种情况又会产生新的限制。

解决:先执行uname -m确认架构。如果是 x86_64,换 x86_64 的 JDK 包;如果输出是armv7l或类似值,说明用户态是 32 位,需要安装对应的 32 位 ARM JDK,而不是强行使用此 aarch64 包。判断用户态位数还有一个方法:执行getconf LONG_BIT,输出 64 表示 64 位用户态。

4.2 运行 java 提示 No such file or directory

现象:文件明明存在,ls -l bin/java能看到可执行权限,执行时却提示No such file or directory。

原因:这个报错通常不是文件不存在,而是动态链接器找不到。aarch64 JDK 需要对应架构的 glibc 基础库,在精简镜像里这些库可能没装全。也有可能是压缩包在传输过程中损坏,解压出来的 ELF 文件不完整。

解决:先执行ldd bin/java,看依赖库哪些 missing。如果是 glibc 相关依赖缺失,用系统自带的包管理器安装基础库;如果file bin/java输出异常,重新解压原压缩包,并重新校验 sha256。顺便提醒一句,解压到 NFS 挂载目录有时也会出现奇怪的执行问题,优先确认文件在本地磁盘。

4.3 时区被默认成 UTC,日志时间不对

现象:应用日志里的时间比本地时间晚 8 小时,排查问题时时间对不上。

原因:很多服务器镜像把系统时区设置为 UTC,JVM 在启动时读取系统时区,除非有显式参数,否则不会自己校正。通过 systemd 启动的服务还会继承更精简的环境变量,你手动在 shell 里export TZ并不会传递到服务进程。

解决:在 systemd 单元文件里显式加入Environment=TZ=GMT+08:00,并在 Java 启动参数里加上-Duser.timezone=GMT+08:00。注意这里的时区写法不依赖具体地名,适合服务器环境使用。改完配置后重启服务,再检查日志时间戳。从这里也能看出,把时区参数写进服务定义,比依赖机器全局设置更稳定,尤其是节点后续被重新初始化时。

4.4 老应用出现 TLS 握手失败

现象:JDK 8 部署的应用调用某个 HTTPS 接口,运行时报SSLHandshakeException或者No appropriate protocol。

原因:8u321 默认启用的 TLS 协议版本和加密套件相对保守,而外部服务的服务端版本更新后,已经关闭了旧版 TLS,甚至强制要求 TLSv1.3。你这边还在用 JDK 8 默认的 TLSv1.1,自然无法完成握手。

解决:先用 JVM 调试参数启动应用,确认握手过程实际走了哪个协议版本。常见做法是将协议版本显式提升到 TLSv1.2,这是在 JDK 8 上最稳妥的目标,因为 TLSv1.3 在 8u321 中支持还不够完整。设置启动参数时,同时设置-Dhttps.protocols=TLSv1.2和-Djsse.enableSNIExtension=true。不要为了绕过问题而直接关闭证书校验,否则安全扫描会让你返工。

4.5 进程内存看起来翻倍了

现象:一台服务器上跑了好几个 Java 服务,监控面板显示各自的内存占用都接近甚至超过-Xmx设置值,看起来像内存泄漏。

原因:-Xmx只限制堆内存,不包含元空间、线程栈、JIT 编译器代码和 GC 相关结构。JDK 8 里元空间默认上限很大,只要应用不停加载类,本地内存就会持续增长。这些内存都会计入 RSS,所以监控显示的值比堆大是正常的,不是漏内存。

解决:在启动参数里显式限制本地内存,比如加-XX:MaxMetaspaceSize=256m,同时用 systemd 的MemoryMax给整个服务限定上限。还有一点,-Xmx不要拍脑袋设成机器内存的四分之三,多个实例叠加后很快会触发 swap。我的习惯是单实例堆大小先给保守值,再用jstat -gc观察实际使用率逐步调整。

5. 进阶:用几分钟给 JDK 8 做一次性能基线验证

服务能跑起来不算完,我还会在正式接入流量前,给新节点做一次快速基线验证。这一步不是为了压测,而是给以后的排错留下一份可对比的数据。

拿到 Java 进程号后,用下面几个命令收集信息:

JAVA_PID=$(pgrep -f app.jar) jcmd $JAVA_PID VM.version jcmd $JAVA_PID VM.system_properties jstat -gcutil $JAVA_PID 1000 5 jmap -heap $JAVA_PID | head -n 30

jcmd VM.version会输出 JVM 版本和启动路径,VM.system_properties则把系统属性和启动参数的关键项打印出来。如果有两台机器配置不一致,这个输出很容易对比出来。jstat -gcutil每秒采样一次,连续 5 次,会把堆的各区域使用率、GC 次数和耗时打印出来,是判断节点是否健康的最直接指标。jmap -heap输出整个堆的配置摘要,比如年轻代大小、老年代大小,这些数据之后可以跟线上出问题时的快照做对比。

做基线有几个注意点。第一,jcmd和jmap需要与目标进程相同用户执行,否则会出现权限不足,这时候用sudo -u appuser执行。第二,jstat -gcutil的采样间隔不要太短,至少 1 秒一次,否则 GC 数据会抖动。第三,基线文件不要只放在终端里,我会保存到/opt/app/baseline/目录,文件名带上日期和节点标识,比如baseline_20240605_node03.txt。

从那以后,我每次部署完都强制走一遍这个流程,把jcmd和jstat的结果存档。因为线上问题往往发生在部署后几周,没有基线就只能靠猜。这五个命令加在一起也就一行脚本,却能让你在半夜排查时少掉不少头发。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询