FEATURED · 精选文章

ARM服务器安装OpenJDK 11:从文件名拆解到容器化避坑全记录

发布时间 / 2026/9/20 15:01:46
来源 / 创域科博编辑部
栏目 / 资讯中心
ARM服务器安装OpenJDK 11:从文件名拆解到容器化避坑全记录 简介这是一份面向Linux/aarch6464位ARM平台的OpenJDK 11更新版JDK安装包压缩包内含完整JDK组件适用于在ARM服务器、嵌入式或云原生环境中进行Java应用的开发、编译与运行具备模块化、文本块、ZGC等Java 11关键特性。包体共508个文件压缩后约182MB主要包含jmod模块描述、so动态链接库、license授权信息、h头文件及javac、java、jshell、jlink等命令行工具解压后形成标准jdk-11.0.109目录结构便于直接配置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.109。拿错一个字符轻则解压后无法执行重则整个服务启动直接段错误。这篇文章我会围绕这个具体的 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按下划线、点和横杠拆开信息其实非常直白字段含义补充说明OpenJDK11UOpenJDK 11 Update 系列U 表示 Update是 AdoptOpenJDK/Temurin 对 OpenJDK 11 的持续更新构建jdk完整 JDK 包包含 javac、jlink、jconsole 等开发工具不是只用于运行的 JREaarch64ARM 64 位架构对应 ARMv8-A 及以上指令集linux目标操作系统这个包面向 Linux 系统hotspotHotSpot 虚拟机OpenJDK 默认 JVM 实现11.0.10Java 版本号代表 OpenJDK 11.0.10 版本9构建号对应完整版本号里的9即 11.0.109tar.gz压缩归档格式gzip 压缩的 tar 包文件名里为什么不直接写9而要写成_9因为加号在 URL 里需要转义成%2B下载软件和脚本处理起来容易出错。所以官方发布文件里统一用下划线替代加号。这个细节在写自动化部署脚本时特别重要如果你手写文件名拼成jdk-11.0.109去下载wget 经常会因为加号处理问题 404。1.2 为什么是 aarch64而不是 arm64aarch64 和 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-openjdkUbuntu 上可以apt install openjdk-11-jdk。但这类系统源里的 JDK 往往存在两个问题一是版本发布滞后安全补丁不一定能第一时间跟进二是发行版会打自己的补丁有时还会修改目录结构比如 Debian 系把 Java 装到/usr/lib/jvm/java-11-openjdk-arm64RHEL 系则放在/usr/lib/jvm/java-11-openjdk-11.0.x.x下面。对于个人开发机来说这些问题无伤大雅。但在生产环境里我遇到过因为系统源里的 JDK 小版本不一致导致测试环境能跑、生产环境跑不了的情况。后来我们干脆在项目里锁定具体的 JDK 构建号也就是类似11.0.109这样精确到 build 的版本。这样整个交付链路里的 JDK 完全一致问题排查范围一下就缩小了。2.2 AdoptOpenJDK / Temurin 构建差异AdoptOpenJDK 是由社区维护的 OpenJDK 发行版后来迁移到 Eclipse 基金会现在叫 Eclipse Temurin是 Adoptium 项目的一部分。它做的关键事情是用标准流程构建 OpenJDK跑完整的测试套件发布带 TCK 认证的二进制包。和系统自带的 OpenJDK 相比Temurin 的包有几个明显特点目录结构统一解压后就是一个jdk-11.0.109目录里面没有乱七八糟的系统库联动。不依赖系统包管理拷到任何兼容的 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_HOME3.1 确认机器架构和系统版本动手之前先确认几件事uname -m # 架构aarch64 就对了 cat /etc/os-release # 查看发行版和版本 nproc # CPU 核数 free -h # 内存JDK 11 完整包解压后大约 300MB运行时不强制要求多大内存但如果你要编译大型项目建议至少 2GB 可用内存。确认架构输出是aarch64后再去核对文件名里的aarch64一致了再继续。3.2 下载、校验和解压下载地址我一般直接走 Adoptium 的 API可以指定最新版也可以指定历史版本。这次要装 11.0.109对应 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 资源。下载完成后强烈建议校验 SHA256echo 发布页提供的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.109 /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.109) OpenJDK 64-Bit Server VM 18.9 (build 11.0.109, mixed mode)这里有一个小技巧用java -XshowSettings:properties -version可以查看当前实际生效的java.homejava -XshowSettings:properties -version 21 | 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 roothost java -version却报java: command not found。解决办法是不要在任务里依赖 profile 的全局配置而是在流水线脚本里显式export JAVA_HOME/usr/local/java/jdk-11 export PATH$JAVA_HOME/bin:$PATH或者在执行命令前 source 一下ssh roothost 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_HOMEJenkins 启动后很可能找不到 Java或者使用系统默认的 JRE。正确的做法是在 Jenkins 服务配置里显式指定EnvironmentJAVA_HOME/usr/local/java/jdk-11 EnvironmentPATH/usr/local/java/jdk-11/bin:/usr/bin:/binMaven 也有类似的问题。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 --compress2 \ --output /opt/jre-min FROM ubuntu:22.04 COPY --frombuilder /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 之间配合--compress2压缩后镜像体积会明显下降。5.3 容器里的时区、字体和 CA 证书这些属于“看不见的依赖”。JDK 自带一个cacerts证书库里面是常用 CA 根证书但公司内网的 HTTPS 证书往往是自己签的Java 里直接用HttpClient访问内网服务时经常报PKIX path building failed。处理办法有两个一是把内网证书导入镜像里的 cacertskeytool -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/localtimeJava 读取时区时退回 UTC于是new Date()格式化出来比本地时间慢 8 小时。解决办法是先装tzdata然后在 Dockerfile 里设置ENV TZAsia/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到底指向哪里。很多环境变量引发的诡异问题这一条命令就能暴露出来。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻