简介:这是一份面向Linux/aarch64(64位ARM)平台的OpenJDK 11更新版JDK安装包,压缩包内含完整JDK组件,适用于在ARM服务器、嵌入式或云原生环境中进行Java应用的开发、编译与运行,具备模块化、文本块、ZGC等Java 11关键特性。包体共508个文件,压缩后约182MB,主要包含jmod模块描述、so动态链接库、license授权信息、h头文件及javac、java、jshell、jlink等命令行工具,解压后形成标准jdk-11.0.10+9目录结构,便于直接配置PATH使用。已有1231人学习下载。从实际使用角度看,该包直接提供aarch64预编译产物,可省去源码构建或交叉编译的复杂依赖问题;同时开源特性允许按需裁剪模块、定制运行时,适合作为学习OpenJDK内部结构、排查Java进程或构建轻量级定制化JRE的基础。 第一次看到OpenJDK11U-jdk_aarch64_linux_hotspot_11.0.10_9.tar.gz这个文件名,很多人会觉得“不就是一个 JDK 压缩包嘛,解压、配置环境变量、完事”。但真正在 Linux 服务器上部署过几轮之后就会发现,这个文件名里每一个字段都在提前告诉你关键信息:这是 OpenJDK 11,目标平台是 Linux 的 aarch64 架构,内置 HotSpot 虚拟机,版本是 11.0.10+9。拿错一个字符,轻则解压后无法执行,重则整个服务启动直接段错误。
这篇文章我会围绕这个具体的 JDK 二进制包,把命名规则、为什么不用系统自带的 OpenJDK、从下载到部署的完整流程,以及我在生产环境里踩过的和容器相关的坑,一次性讲透。适合两类人:一类是刚接手 ARM 架构服务器、需要手动装 JDK 的运维同学;另一类是准备把 Java 应用容器化,又不想直接依赖官方镜像、希望自己掌控 JDK 构建的研发同学。如果你拿到的是 x86 服务器的包,文件名里大概率是 x64;如果你没配好 JAVA_HOME,后续 Maven、Jenkins 很可能直接找不到编译器。这些我都遇过,所以这篇不只是安装教程,更是一份避坑记录。
1. 文件名拆解:这个 tarball 到底装的是什么
1.1 逐个字段拆开看
把OpenJDK11U-jdk_aarch64_linux_hotspot_11.0.10_9.tar.gz按下划线、点和横杠拆开,信息其实非常直白:
| 字段 | 含义 | 补充说明 |
|---|---|---|
| OpenJDK11U | OpenJDK 11 Update 系列 | U 表示 Update,是 AdoptOpenJDK/Temurin 对 OpenJDK 11 的持续更新构建 |
| jdk | 完整 JDK 包 | 包含 javac、jlink、jconsole 等开发工具,不是只用于运行的 JRE |
| aarch64 | ARM 64 位架构 | 对应 ARMv8-A 及以上指令集 |
| linux | 目标操作系统 | 这个包面向 Linux 系统 |
| hotspot | HotSpot 虚拟机 | OpenJDK 默认 JVM 实现 |
| 11.0.10 | Java 版本号 | 代表 OpenJDK 11.0.10 版本 |
| 9 | 构建号 | 对应完整版本号里的+9,即 11.0.10+9 |
| tar.gz | 压缩归档格式 | gzip 压缩的 tar 包 |
文件名里为什么不直接写+9而要写成_9?因为加号在 URL 里需要转义成%2B,下载软件和脚本处理起来容易出错。所以官方发布文件里统一用下划线替代加号。这个细节在写自动化部署脚本时特别重要,如果你手写文件名拼成jdk-11.0.10+9去下载,wget 经常会因为加号处理问题 404。
1.2 为什么是 aarch64,而不是 arm64
aarch64 和 arm64 指的是同一套 64 位 ARM 指令集,但名字来源不同。aarch64 是 Linux 内核、GNU 工具链和 ELF 文件格式里使用的标准名称,而 arm64 更多是 ARM 公司营销和技术文档里的叫法。所以你在 Linux 机器上执行uname -m,输出通常都是aarch64,而不是arm64。JDK 包命名也就跟着系统标准走。
这个字段直接决定了包能不能用。华为鲲鹏、飞腾、AWS Graviton、Ampere Altra 这些服务器,以及在 ARM 虚拟机上跑的 Linux,基本都是 aarch64。如果你在一台 ARM 服务器上下载了x64的 JDK,执行时大概率会看到:
-bash: ./java: cannot execute binary file: Exec format error所以拿到一个 tar.gz,第一步不要急着解压,先uname -m看一眼架构。
1.3 HotSpot 和 OpenJ9,选谁更省心
Adoptium 下载页面通常每个版本会提供两种 JVM 实现:HotSpot 和 OpenJ9。HotSpot 是 OpenJDK 的标准虚拟机,Oracle 和大多数发行版默认使用它,JIT 编译、内存管理、监控体系都最成熟。OpenJ9 是 IBM 开源的 JVM,特点是启动速度快、内存占用相对低,适合云原生和资源受限场景。
我个人的建议是:生产环境老老实实用 HotSpot。原因很简单,绝大多数 Java 监控工具、APM 探针、性能分析器都优先针对 HotSpot 做了适配,OpenJ9 在某些库上会出现兼容性问题。除非你明确测过应用在 OpenJ9 下的内存收益,否则不要为了省一点内存去冒险。
2. 为什么放着系统源不用,非要手动装这个包
2.1 发行版自带 OpenJDK 的版本滞后问题
很多 Linux 发行版都提供 OpenJDK 包,CentOS 上可以用yum install java-11-openjdk,Ubuntu 上可以apt install openjdk-11-jdk。但这类系统源里的 JDK 往往存在两个问题:一是版本发布滞后,安全补丁不一定能第一时间跟进;二是发行版会打自己的补丁,有时还会修改目录结构,比如 Debian 系把 Java 装到/usr/lib/jvm/java-11-openjdk-arm64,RHEL 系则放在/usr/lib/jvm/java-11-openjdk-11.0.x.x下面。
对于个人开发机来说,这些问题无伤大雅。但在生产环境里,我遇到过因为系统源里的 JDK 小版本不一致,导致测试环境能跑、生产环境跑不了的情况。后来我们干脆在项目里锁定具体的 JDK 构建号,也就是类似11.0.10+9这样精确到 build 的版本。这样整个交付链路里的 JDK 完全一致,问题排查范围一下就缩小了。
2.2 AdoptOpenJDK / Temurin 构建差异
AdoptOpenJDK 是由社区维护的 OpenJDK 发行版,后来迁移到 Eclipse 基金会,现在叫 Eclipse Temurin,是 Adoptium 项目的一部分。它做的关键事情是:用标准流程构建 OpenJDK,跑完整的测试套件,发布带 TCK 认证的二进制包。
和系统自带的 OpenJDK 相比,Temurin 的包有几个明显特点:
- 目录结构统一,解压后就是一个
jdk-11.0.10+9目录,里面没有乱七八糟的系统库联动。 - 不依赖系统包管理,拷到任何兼容的 Linux 系统上解压就能用。
- 发布周期和上游社区同步,安全更新比较及时。
我当时在 ARM 服务器上部署时选它,还有一个原因是官方 API 支持直接拉取最新或指定版本的 tar.gz,很适合写自动安装脚本。
2.3 什么场景必须用这类 tar.gz 包
不是所有场景都要手动装,但下面几类场景用 tar.gz 会省事很多:
- 离线内网服务器:无法访问系统源或官方下载地址时,直接拷贝 tar.gz 进去解压即可。
- 多版本 JDK 共存:系统包管理器会把 default-java 改来改去,tar.gz 可以随意放在
/usr/local/java下,用软链切换。 - 容器基础镜像:想在 Dockerfile 里安装指定 JDK,而不是每次 rebuild 都受系统源版本变化影响。
- 安全基线要求:公司要求必须使用某一个小版本的 JDK,手动安装能精确锁定。
3. 一步一步装到 /usr/local 并配置 JAVA_HOME
3.1 确认机器架构和系统版本
动手之前,先确认几件事:
uname -m # 架构,aarch64 就对了 cat /etc/os-release # 查看发行版和版本 nproc # CPU 核数 free -h # 内存JDK 11 完整包解压后大约 300MB,运行时不强制要求多大内存,但如果你要编译大型项目,建议至少 2GB 可用内存。确认架构输出是aarch64后,再去核对文件名里的aarch64,一致了再继续。
3.2 下载、校验和解压
下载地址我一般直接走 Adoptium 的 API,可以指定最新版,也可以指定历史版本。这次要装 11.0.10+9,对应 URL 是:
wget -c \ "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_aarch64_linux_hotspot_11.0.10_9.tar.gz" \ -O /tmp/jdk11.tar.gz注意 URL 里的+要写成%2B,否则 GitHub 可能解析不到对应的 release 资源。下载完成后,强烈建议校验 SHA256:
echo "发布页提供的sha256值 /tmp/jdk11.tar.gz" | sha256sum -c -这一步很容易被跳过,但生产环境我从不省略,避免下载到被篡改或者损坏的文件。确认无误后解压:
mkdir -p /usr/local/java tar -xzf /tmp/jdk11.tar.gz -C /usr/local/java ln -sfn /usr/local/java/jdk-11.0.10+9 /usr/local/java/jdk-11为什么要做软链?因为后面升级 JDK 时,只需要解压新版本到同一个目录,然后把软链指过去,应用配置里的JAVA_HOME完全不用动。而直接改目录名的方式,每一次升级都要改配置,还容易漏。
3.3 环境变量配置与 alternatives 注册
环境变量我推荐写到/etc/profile.d/下,这样所有登录用户都能用:
cat > /etc/profile.d/jdk11.sh <<'EOF' export JAVA_HOME=/usr/local/java/jdk-11 export PATH=$JAVA_HOME/bin:$PATH EOF chmod 644 /etc/profile.d/jdk11.sh source /etc/profile.d/jdk11.sh但如果你的机器上之前用yum或apt装过其他版本的 OpenJDK,只配环境变量还不够。很多系统脚本会直接调用/usr/bin/java,而/usr/bin/java通常是指向update-alternatives管理的符号链接。这时候要把你的 JDK 注册进去:
update-alternatives --install /usr/bin/java java $JAVA_HOME/bin/java 2000 update-alternatives --install /usr/bin/javac javac $JAVA_HOME/bin/javac 2000 update-alternatives --config java优先级 2000 足够让新的 JDK 成为默认。这样后续如果想切换回其他版本,一条update-alternatives --config java就能搞定,不会影响依赖旧版 JDK 的服务。
3.4 验证安装结果
配置之后,重新打开一个终端或者执行bash -l加载登录环境的配置,然后验证:
java -version javac -version echo $JAVA_HOME which java readlink -f $(which java)期望输出类似:
openjdk version "11.0.10" 2021-01-19 OpenJDK Runtime Environment 18.9 (build 11.0.10+9) OpenJDK 64-Bit Server VM 18.9 (build 11.0.10+9, mixed mode)这里有一个小技巧:用java -XshowSettings:properties -version可以查看当前实际生效的java.home:
java -XshowSettings:properties -version 2>&1 | grep java.home只要显示/usr/local/java/jdk-11,就说明当前进程用的确实是你刚装的 JDK。这个命令在排查“明明改了 JAVA_HOME 却不生效”的场景时特别管用。
4. 安装后最容易踩的五个坑,我一个个说
4.1 环境变量没生效的“非登录 Shell”陷阱
改了/etc/profile.d/jdk11.sh之后,已经打开的终端不会自动加载,这是常识。更隐蔽的是:当你通过ssh host command这种方式远程执行命令时,SSH 为这条命令启动的是非登录、非交互 Shell,它不会读取/etc/profile,更不会读/etc/profile.d/下的脚本。
所以你在 CI/CD 流水线里经常会遇到一个诡异现象:手动 SSH 上去java -version一切正常,但流水线执行ssh root@host "java -version"却报java: command not found。解决办法是不要在任务里依赖 profile 的全局配置,而是在流水线脚本里显式:
export JAVA_HOME=/usr/local/java/jdk-11 export PATH=$JAVA_HOME/bin:$PATH或者在执行命令前 source 一下:
ssh root@host "source /etc/profile.d/jdk11.sh && java -version"这个坑排查起来非常耗时间,我建议从一开始就在自动化脚本里把JAVA_HOME写死,不要依赖服务器全局变量。
4.2 系统里旧 Java 抢占了 PATH
如果你的机器以前装过 OpenJDK 8,即使你配好了/etc/profile.d/jdk11.sh,也可能遇到java -version显示的是 1.8。原因通常是/usr/bin/java这个符号链接还指向/etc/alternatives/java,而 alternatives 的默认条目还是旧版本。
用which java和readlink -f $(which java)可以快速定位。解决办法就是执行update-alternatives --config java,选择优先级为 2000 的那个路径。如果你不希望系统里残留别的 JDK,也可以直接:
rm -f /usr/bin/java /usr/bin/javac ln -s $JAVA_HOME/bin/java /usr/bin/java ln -s $JAVA_HOME/bin/javac /usr/bin/javac但我更推荐 alternatives,因为它的切换和回滚都更优雅,你不需要记住原来的 JDK 路径。
4.3 下载错架构:Exec format error
这是我见过最多的低级错误。明明服务器是 aarch64,下载包时却选了 x64,解压后执行java报:
-bash: /usr/local/java/jdk-11/bin/java: cannot execute binary file: Exec format error出现这个提示,不需要重装系统。先用uname -m确认架构,再核对文件名里的aarch64。有时候是下载页面默认给了 x64 的链接,手快就直接 wget 了。还有一个典型场景:在一台 x86 服务器上用docker build --platform linux/arm64构建镜像,构建机本身执行不了 ARM 二进制,但镜像里拷进去的 JDK 如果是 x64 的,跑到 ARM 机器上一样报这个错。所以确认架构要分两层:镜像目标架构和宿主机架构。
4.4 glibc 版本太老导致 JVM 起不来
JDK 11 的官方二进制对系统 glibc 有最低版本要求,通常是 glibc 2.17 及以上。CentOS 7 能满足,但一些嵌入式 ARM 板子的裁剪系统、老 NAS 系统,或者为了省空间被精简过的 rootfs,很可能不满足。
这种问题表现为启动时直接报:
./java: /lib/aarch64-linux-gnu/libc.so.6: version `GLIBC_2.17' not found或者干脆段错误。先执行ldd --version查看系统的 glibc 版本,再决定是升级系统基础库,还是换低版本的 JDK。升级 glibc 风险很大,如果不是必须,我更倾向于选一个与系统兼容的旧 JDK 版本。在 ARM 服务器上尤其要注意,很多板子的系统库被裁剪过,不只是缺 glibc,可能还缺 libz、libgcc_s 这些动态库。
4.5 Jenkins 和 Maven 绕过 profile.d 的问题
Jenkins 以 systemd 服务方式运行时,它的环境变量来自/etc/sysconfig/jenkins或者 service 文件,根本不会读取/etc/profile.d。如果你只在服务器登录用户的 profile 里配置了 JAVA_HOME,Jenkins 启动后很可能找不到 Java,或者使用系统默认的 JRE。
正确的做法是在 Jenkins 服务配置里显式指定:
Environment="JAVA_HOME=/usr/local/java/jdk-11" Environment="PATH=/usr/local/java/jdk-11/bin:/usr/bin:/bin"Maven 也有类似的问题。mvn脚本本身会优先使用JAVA_HOME,但 IDE 内嵌的 Maven 可能使用 IDE 自带的 JRE,导致编译用的 JDK 版本和命令行不一致。遇到这类情况,检查三点:IDE 里的 JDK 配置、Maven 的运行 JDK、以及系统变量的 JAVA_HOME,保证三者指向同一个版本。
5. 容器化和瘦身:把这个 JDK 塞进镜像里
5.1 用 tar.gz 自建 JDK 基础镜像
很多内部镜像仓库没有现成的eclipse-temurin官方镜像,这时候直接把 tar.gz 带进 Dockerfile 是最可控的方案:
FROM ubuntu:22.04 RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates tzdata fontconfig curl \ && curl -fSL -o /tmp/jdk.tgz \ "https://api.adoptium.net/v3/binary/latest/11/ga/linux/aarch64/jdk/hotspot/normal/eclipse" \ && mkdir -p /opt/java \ && tar -xzf /tmp/jdk.tgz -C /opt/java \ && ln -sfn /opt/java/jdk-11* /opt/java/jdk-11 \ && rm -f /tmp/jdk.tgz ENV JAVA_HOME=/opt/java/jdk-11 ENV PATH=$JAVA_HOME/bin:$PATHca-certificates、tzdata、fontconfig这三个看着不是 Java 运行必需的组件,但都加上能避免后面一堆头疼问题。比如fontconfig,如果你的应用会生成验证码、条形码或者图表,只要容器里没有字体配置,Java 的图形模块很可能直接抛HeadlessException或者中文全部变方块。tzdata影响默认时区,ca-certificates影响 HTTPS 证书校验,后面会详细说。
5.2 用 jlink 裁剪运行时,镜像从 300MB 降到 80MB
完整 JDK 解压后 300MB 左右,很多应用其实只需要 JRE,甚至只需要几个核心模块。JDK 9 开始自带的jlink工具就是干这个的。用多阶段构建:
FROM eclipse-temurin:11-jdk AS builder RUN jlink \ --add-modules java.base,java.sql,java.naming,java.management,java.desktop \ --strip-debug --no-man-pages --no-header-files --compress=2 \ --output /opt/jre-min FROM ubuntu:22.04 COPY --from=builder /opt/jre-min /opt/jre-min ENV JAVA_HOME=/opt/jre-min ENV PATH=$JAVA_HOME/bin:$PATH--add-modules里的模块一定要根据应用实际依赖来定。最简单的java.base是必须的,Spring Boot 应用通常还需要java.sql、java.naming、java.management。如果用到 HTTPS 客户端,可能还要加jdk.crypto.ec;用到 RMI,就要加java.rmi。可以用jdeps分析应用 jar 包的模块依赖:
jdeps --print-module-deps --ignore-missing-deps app.jar它会直接输出你需要的模块列表,把它填到jlink --add-modules后面。少写了模块会在运行期抛ModuleNotFoundException,多写了模块又白裁剪。裁剪完的 JRE 通常在 80-120MB 之间,配合--compress=2压缩后,镜像体积会明显下降。
5.3 容器里的时区、字体和 CA 证书
这些属于“看不见的依赖”。JDK 自带一个cacerts证书库,里面是常用 CA 根证书,但公司内网的 HTTPS 证书往往是自己签的,Java 里直接用HttpClient访问内网服务时经常报PKIX path building failed。处理办法有两个:一是把内网证书导入镜像里的 cacerts:
keytool -import -trustcacerts \ -alias internal-ca \ -file /path/to/internal-ca.crt \ -keystore $JAVA_HOME/lib/security/cacerts \ -storepass changeit二是在应用启动参数里指定自定义 trustStore。注意不要和操作系统的/etc/ssl/certs搞混,Java 默认不读系统证书库,除非显式把javax.net.ssl.trustStore指过去。
时区问题在国内部署场景尤其常见。基础镜像默认没有/etc/localtime,Java 读取时区时退回 UTC,于是new Date()格式化出来比本地时间慢 8 小时。解决办法是先装tzdata,然后在 Dockerfile 里设置:
ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone这些细节不像 JDK 安装本身那么显眼,但上线后它们会成为最磨人的问题。我建议在写基础镜像时就统一处理掉,而不是等应用报错再逐个排查。
这个 tar.gz 我在 ARM 服务器上装过很多次,从最开始只执行java -version就以为装完了,到后来把容器、时区、证书这些坑都填平,最大的体会是:JDK 安装从来不是解压完就结束的事,环境变量、系统库、运行时依赖每一项都可能在上线当天反咬你一口。还有个小技巧,装完顺手跑一下java -XshowSettings:properties -version,看一眼java.home到底指向哪里。很多环境变量引发的诡异问题,这一条命令就能暴露出来。
本文还有配套的精品资源,点击获取