ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ARM 服务器上安装配置 Java 8:jdk-8u391-linux-aarch64 全指南

ARM 服务器上安装配置 Java 8:jdk-8u391-linux-aarch64 全指南

简介:面向基于ARM架构的Linux(aarch64)服务器、嵌入式设备与边缘计算终端,这份jdk-8u391-linux-aarch64.tar是Java 8更新391版的完整安装压缩包,可帮助开发者在ARM平台上快速搭建Java编译、运行与调试环境,解决常规x86 JDK无法在ARM设备上安装的兼容性问题。包内共403个文件,压缩后大小约71.23MB,主要由java、javac、keytool等可执行工具,so动态链接库,jar核心库,properties配置,ttf字体以及HTML、man手册等构成,覆盖JDK运行所需的命令行工具、类库、界面资源与认证材料,目录结构清晰,便于按模块定位。其中可执行工具集成了编译器、调试器、打包与证书管理等功能,动态库支撑JVM底层运行,配置与字体文件保障图形界面和网络服务正常工作。截至目前已有1486人学习下载。解压后配合JAVA_HOME、PATH等环境变量配置即可投入使用,适合需要离线安装、批量部署或后期按模块裁剪的开发者使用,同时内置的man手册与HTML文档页可为二次开发和排错提供参考。

1. ARM 服务器上的 Java 8:兜兜转转还得用它

这几年 ARM 架构的机器在云上和边缘侧越来越多,跑 Linux aarch64 环境早就不是稀罕事。但真正把 Java 应用往 ARM 上迁移时,很多人会卡在同一个问题:手上那套跑得好好的 JDK 8 在 x86_64 上能用,换个架构就抓瞎。这个 jdk-8u391-linux-aarch64.tar 就是解决这个场景的:它是一份带标准目录结构的 Java 8 开发工具包二进制包,专门给 aarch64 Linux 环境用。简单说,你需要在一个不支持 x86 二进制的 ARM Linux 上把 Java 8 跑起来,这个包就是最直接的那条路。适合谁?在 ARM 云主机、开发板、国产化服务器上维护老项目的工程师,或者正准备把 Spring Boot 等老版本应用迁移到 ARM 环境的人。它不解决业务问题,但解决第一道门槛:让 java 命令先能跑起来。

2. 认识 jdk-8u391-linux-aarch64.tar:包内结构与选型逻辑

2.1 从 x86_64 切到 aarch64,JDK 分发版差在哪

先明确一点:JDK 是跟 CPU 指令集强绑定的东西。x86_64 的 JDK 是 64 位的 x86 指令,拿到 aarch64 机器上完全没法执行,反过来也一样。很多人第一反应是“都是 Linux 的,应该通用”,结果解压后一执行,直接报 “Exec format error” 或者 “cannot execute binary file”,这就是架构不匹配。

aarch64 是 ARM 64 位指令集,常见叫法还有 ARM64。这个 jdk-8u391-linux-aarch64.tar 面就非常明确:Linux 平台、aarch64 架构、Java 8 的 u391 版本。u391 是 Java 8 一个重要的更新版本,修了不少安全漏洞,也是目前很多维护项目会选的一个基准版本。

选型的时候,很多人会纠结到底用 OpenJDK 还是商业发行版。实际上这个 tar 的结构跟标准 JDK 完全一致,包含 bin、lib、include、jre 等目录,不存在“特殊版本”的额外配置。我一般优先用这种官方目录结构的二进制包,原因很直接:无论你后面配 Maven、Gradle 还是打 Docker 镜像,它跟 x86_64 上的 JDK 路径结构完全对齐,迁移成本低到可以忽略。

2.2 包内目录结构和关键文件

拿到这个 tar 文件后,先别急着解压,看一眼包结构心里更有底。我们可以用 tar 命令列一下内容:

tar -tzf jdk-8u391-linux-aarch64.tar | head -20

输出是包内文件清单的头 20 条。命令的-t表示列出内容而不是解压,-z表示通过 gzip 解压读取,-f指定文件名。看到类似jdk1.8.0_391/bin/java、jdk1.8.0_391/lib/rt.jar这种结构,就确认是标准 JDK 目录包。

目录里最关键的内容是这几个:

路径作用
bin/javaJava 运行时入口,执行java -version的就是它
bin/javacJava 编译器,用于把.java编译成.class
lib/rt.jarJava 运行时核心类库
lib/tools.jar编译器和一些开发工具依赖的类库
jre/独立运行时目录,有的程序会单独指定 JRE 路径
include/JNI 开发时需要的 C/C++ 头文件

这套结构跟 x86_64 版 JDK 几乎一致,所以你不需要学一套新的部署方式。对于纯跑应用的人来说,bin 和 lib 就够用了;如果你还要做 JNI 或者写 Java agent,include 目录里的头文件同样不能少。

另外,每个文件本身是否完整值得单独验证一下,这放在下一章一起说。

3. 安装与配置:从 tar 包到可用的 java 命令

3.1 校验、解压、目录归属

拿到 tar 包后,第一步永远是校验完整性。常见做法是先核对 SHA256 校验值,确保传输过程没有损坏。我习惯先跑一下:

sha256sum jdk-8u391-linux-aarch64.tar

把这行命令输出的 64 位十六进制字符串,跟下载时发布的校验值逐位对比。这一步是玄学重灾区,很多人图省事跳过,结果解压后 java 二进制损坏,报了各种莫名其妙的java.lang.UnsatisfiedLinkError,最后才想起来校验文件。

校验通过后,解压到 /usr/local 或者自己的用户目录。以 /usr/local 为例:

sudo tar -xzf jdk-8u391-linux-aarch64.tar -C /usr/local

-x是解压,-z走 gzip 解压,-C指定解压目标目录。解压完成后,目录会出现在/usr/local/jdk1.8.0_391。这个路径是不是固定?取决于 tar 包内部的顶层目录名。用tar -tzf列过之后就能确认,我这里是这个版本常见的叫法。

建议把目录归属改成专用用户,避免后面应用以 root 跑。常见做法是创建一个java用户,或者直接用已有业务用户:

sudo chown -R java:java /usr/local/jdk1.8.0_391

如果只是单机本地开发,这一步可以省,但服务器上还是建议做,毕竟后面 JAVA_HOME 环境变量可能被多个服务引用,权限太开放容易出问题。

3.2 设置 JAVA_HOME 与 PATH,让系统认账

解压完成不代表 java 命令能用,因为 shell 默认去/usr/bin找可执行文件。最常见的做法是把 JDK 的 bin 目录追加到 PATH 前面。以 bash 为例,编辑用户级配置文件:

vim ~/.bashrc

在文件末尾加以下几行:

export JAVA_HOME=/usr/local/jdk1.8.0_391 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar

第一行定义 JAVA_HOME 为 JDK 根目录,很多框架和工具,比如 Tomcat、Maven、Gradle,都会读取这个变量来确定用哪套 JDK。第二行把 JDK 的 bin 目录插到 PATH 最前面,这样执行 java 时优先找到我们指定的版本。第三行设置 CLASSPATH,把当前目录和核心类库加进去,老项目里很常见。

修改完后让配置生效:

source ~/.bashrc

注意,source只在当前 shell 生效,如果开了新终端,那新终端自己会读 .bashrc。服务器上如果跑的是 systemd 服务,不能只靠 .bashrc,需要把 JAVA_HOME 写进服务的 Environment 配置,或者写到 /etc/environment。这个细节后面避坑章还会说。

3.3 验证安装:java -version 的输出该长什么样

配置完先别急着跑项目,用最简单的命令确认环境真的对了:

java -version

正确的输出应该是类似这样的:

java version "1.8.0_391" Java(TM) SE Runtime Environment (build 1.8.0_391-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.391-b13, mixed mode)

看到64-Bit Server VM就说明架构对了,因为这是 aarch64 的 64 位虚拟机。如果你的输出里写着什么java version "1.7.0"或者OpenJDK Runtime Environment但版本数字不对,多半是 PATH 里还有别的 JDK 占着优先级。

同时确认编译器也正常:

javac -version

输出javac 1.8.0_391即可。把这两条命令组合起来,用which java看一下实际路径:

which java

如果输出是/usr/local/jdk1.8.0_391/bin/java,说明 PATH 生效。如果输出是/usr/bin/java,那就是系统里装过别的 JDK,你在第 4 章会看到怎么处理。

4. 避坑:ARM 下 Java 8 的常见翻车现场

4.1 现象:明明解压了,java -version 报错 “cannot execute binary file”

第一次在 aarch64 机器上跑java -version,直接报错cannot execute binary file: Exec format error。

原因:文件架构和系统架构不一致。最常见的情况是系统装的是 aarch64 内核,但你下载的 tar 是 x86_64 版;或者是 32 位 ARM 系统,但 JDK 是 aarch64 的。用uname -m看一下系统架构,若输出aarch64,就必须用 aarch64 的 JDK 包。解决:确认当前系统的架构,再重新拿对的 tar 包。这个包的文件名里已经写了 linux-aarch64,只要确认下载的不是 x86 版就行。

4.2 现象:No such file or directory,但文件明明存在

执行/usr/local/jdk1.8.0_391/bin/java时,提示 No such file or directory,但 ls 一下文件明明在。

原因:这个提示不是指 java 文件不存在,而是指 java 这个可执行文件依赖的动态链接库找不到。在 aarch64 Linux 上,常见原因是系统缺少 64 位 ARM 版的 glibc,或者系统是精简版内核,连基本的ld-linux-aarch64.so.1都没有。解决:检查系统是否安装了对应架构的 glibc:

ldd /usr/local/jdk1.8.0_391/bin/java

如果输出里一堆not found,就说明动态库缺失。在基于 Debian 的系统上,补装基础运行库:

sudo apt-get install libc6:arm64

然后重跑 ldd 确认没有 pending 的依赖项。

4.3 现象:版本对不上,程序却用到了系统自带的 OpenJDK

配好 JAVA_HOME 后,echo $JAVA_HOME显示正确,但跑应用时日志里 Java 版本还是旧的。

原因:应用是通过 systemd 启动的,早读过了/etc/init.d脚本或 service 文件里的环境变量,而source ~/.bashrc并没有改掉服务级的环境。解决:在 systemd service 文件里显式写环境变量:

Environment=JAVA_HOME=/usr/local/jdk1.8.0_391 Environment=PATH=/usr/local/jdk1.8.0_391/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

改完执行systemctl daemon-reload再重启服务。如果项目用脚本启动,把 export 写在脚本开头,别依赖用户 profile。

4.4 现象:JAVA_HOME 配了,脚本还是找不到 java

localhost 上 java 能跑,但一执行项目里的 start.sh 就提示java: command not found。

原因:脚本可能是通过/bin/sh执行的,而 /bin/sh 在某些系统上并不加载用户的 .bashrc,甚至也不加载 .profile。解决:在脚本开头显式指定环境:

#!/bin/bash export JAVA_HOME=/usr/local/jdk1.8.0_391 export PATH=$JAVA_HOME/bin:$PATH

把这一行作为启动脚本的第一部分,保证无论谁调用都能拿到路径。还有个更稳的办法:直接在脚本里调 JDK 的全路径/usr/local/jdk1.8.0_391/bin/java,不依赖 PATH。

4.5 现象:GC 和内存参数在 ARM 上突然不生效

在 x86 环境上调好的 JVM 参数,比如-Xms2048m -Xmx2048m -XX:+UseG1GC,迁到 ARM 上以后,有时日志里 GC 策略不对劲,或者启动直接失败。

原因:不同架构上 JVM 的默认 GC 有差异,JDK 8 在 aarch64 上的默认 GC 是分代并行 GC 或 G1,取决于具体版本和内存大小。G1 参数在 ARM 上表现正常,但如果崩溃了,先排查是不是 C1/C2 编译器阈值参数写太激进。解决:先用java -XX:+PrintFlagsFinal -version | grep GC看默认 GC,再按需显式声明。我一般会在启动脚本里把 GC 和堆参数写成显式组合,不靠默认值。ARM 上比较稳的配置是:

java -Xms1024m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200

如果 JVM 直接无法启动,先去掉所有 GC 参数只留堆大小,逐层加回来,定位是哪一个是雷。

5. 让 JDK 8 在一个项目里真正跑起来:验证脚本与进阶配置

5.1 快速自检脚本:把 JVM 参数和架构一次性打出来

环境配好后,我习惯性写一个自检脚本,放在项目根目录下,每次部署后执行一遍,直接确认当前跑的是哪套 JDK、架构、堆参数。

#!/bin/bash echo "=== Architecture Check ===" uname -m echo "=== Java Version ===" java -version 2>&1 echo "=== JAVA_HOME ===" echo $JAVA_HOME echo "=== Effective JDK Path ===" which java echo "=== Default GC ===" java -XX:+PrintFlagsFinal -version 2>&1 | grep -i "UseG1GC\|UseParallelGC\|UseSerialGC\|UseConcMarkSweepGC"

这段脚本做了四件事:第一块输出系统架构,确认是不是 aarch64;第二块打印 Java 版本;第三块输出 JAVA_HOME;第四块打印哪个 GC 标志被启用。输出里如果出现bool UseG1GC = true,说明 G1 是当前生效的,而其他同类的变量为 false。这套脚本的价值在部署新环境时一目了然,不用再挨个手敲命令。

5.2 配合 Maven/Gradle 把编译目标钉在 Java 8

跑老项目时,光有 JDK 还不行,构建工具经常会悄悄用更高版本的 JDK 来编译,结果编译出来的 class 文件版本不对,运行时抛UnsupportedClassVersionError。

Maven 项目里,我一般这样约束编译目标和源码版本。在 pom.xml 的 properties 区域显式声明:

<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

这三个配置分别指定源码兼容 Java 8 语法、编译目标字节码版本为 1.8、编码格式为 UTF-8。如果你用的编译器插件是默认版本,加这两个属性就够;如果还不行,就显式指定编译插件版本,并配置release=8。对于 Gradle 项目,在 build.gradle 里写:

java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 }

这段的作用同样是锁定编译级别。这一步容易被忽略,但实际踩过的人都知道:代码在 x86 上编译没问题,换到 ARM 上构建后跑不起来,十有八九是构建工具用了宿主机默认 JDK,根本没读你配的 JAVA_HOME。

5.3 把关键启动参数固定成一个 start.sh 模板

项目里我习惯放一份 start.sh,不依赖系统环境,直接把 JDK 路径和参数写死,省得到每个新环境都要重新猜路径。一个适用于 JDK 8 在 aarch64 Linux 上的启动模板是这样:

#!/bin/bash export JAVA_HOME=/usr/local/jdk1.8.0_391 export PATH=$JAVA_HOME/bin:$PATH APP_HOME=/opt/myapp MAIN_CLASS=com.example.MyApp $JAVA_HOME/bin/java \ -Xms1024m \ -Xmx1024m \ -XX:+UseG1GC \ -XX:MaxMetaspaceSize=256m \ -Dfile.encoding=UTF-8 \ -Djava.library.path=$APP_HOME/lib \ -cp "$APP_HOME/lib/*:$APP_HOME/classes" \ $MAIN_CLASS

-Xms 和 -Xmx 是堆内存初始值和最大值,建议保持一致防止动态扩容导致性能抖动;-XX:+UseG1GC 显式选择 G1;-Dfile.encoding 是为了让老代码里依赖默认字符集的逻辑不乱踩坑;-Djava.library.path 是 JNI 或 OpenCV 一类 native 库的定位路径。-cp把项目依赖 jar 包和编译后的 class 目录全接上。这套写法不管 shell 是从哪种模式调用,都能保证 java 命令来自我们自己指定的 JDK。

5.4 收尾:真正的验证,是部署一次完整应用

说到最后,我还是留一个习惯:哪怕 java -version 已经完美了,我仍然会强迫自己走一遍真实应用的启动链路,而不是停在“命令能跑”就收工。因为 ARM 上 JDK 8 的坑,往往不在 java 本身,而在 shared library、字符集、时区数据这些周边环境里。从那以后,每次新部署我都强制跑一次完整应用启动,然后用jcmd打一次线程快照,确认 JVM 真的活起来,再让测试进来。希望帮到你,这套 JDK 8 在 aarch64 上跑老项目的套路,试过就知道比想象中省事得多。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进