最近帮同事在一台 M1 MacBook Air 上搭 Java 开发环境,折腾了整整一下午。打开 ARM 版 CentOS 7 虚拟机,准备装 JDK 的时候才发现,网上的教程几乎全是 x86 架构的,照抄根本跑不起来。今天把这套“M1 + ARM 版 CentOS 7 安装 JDK”的完整过程和踩坑记录整理出来,希望能帮到同样在苹果芯片上折腾 ARM 系统的朋友。
先说清楚这套东西到底是什么。M1 芯片是苹果自研的 ARM 架构处理器,跑的是 macOS,跟传统的 Intel x86 平台完全不同。ARM 版 CentOS 7 是专门为 ARMv8-A 架构(也就是 aarch64)编译的 Linux 发行版,常用于鲲鹏、飞腾这些 ARM 服务器,现在也能在 M1 Mac 上通过虚拟化跑起来。JDK 则是 Java 运行和编译的必备环境,这里装的是 aarch64 版本的 OpenJDK。这篇文章适合三类人看:一是 macOS 用户想在本地模拟 ARM 服务器环境做开发和测试,二是在 ARM 服务器上部署 Java 应用但找不到头绪的开发者,三是单纯想在苹果芯片上跑 Linux 虚拟机玩一玩的技术爱好者。
1. 整体方案设计与思路拆解
1.1 为什么要用 ARM 版 CentOS 7 而不是 x86 版
我自己在开始之前也纠结过一个问题:既然 M1 Mac 能通过 QEMU 模拟 x86,为什么不直接装 x86 版的 CentOS 7 镜像,省得找 ARM 镜像,教程还多?试过就知道这条路基本走不通。QEMU 在 Apple Silicon 上模拟 x86 是纯二进制翻译,性能和兼容性都不如原生 ARM 虚拟化来得好。
这么说吧,M1 的 CPU 核心本身是 ARMv8 架构,跑 ARM 指令集是零翻译、直接执行,虚拟机里能直接发挥硬件性能;但如果跑 x86 指令,就需要 TCG 动态二进制翻译,每条指令都要翻译成本地指令再执行,CPU 开销至少翻一倍,跑编译任务能明显感觉到卡顿。真实部署里,ARM 服务器(鲲鹏、飞腾、AWS Graviton)用的都是 aarch64 镜像,你要是本地用 x86 版本,等到上线部署的时候,环境差异会带来一堆莫名其妙的坑。
所以结论很直接:M1 上装虚拟机跑 Linux,要么选 ARM 版的 Ubuntu,要么选 ARM 版的 CentOS 7,就是别选 x86 镜像。
1.2 M1 上虚拟机方案选型:UTM vs Parallels vs QEMU 命令行
在 M1 上跑 ARM 虚拟机的方案,主流的就三选一。
Parallels Desktop 是对 Mac 用户最友好的。它内置了 ARM 版 Linux 的预配置镜像,装 CentOS 7 的时候可以做到几分钟搞定,基本上开箱即用。但问题是完整版要付费,试用期过了得续。如果你只是临时体验一把,可以直接用它。
QEMU 命令行方案最不推荐给新手。你得自己写启动参数、指定 UEFI 固件、配置 virtio 设备,任何一个参数写错都启动不了,调试成本太高。适合那种要在 CI 里做自动化测试的,不适合手工操作。
UTM 是我最后选择的方案。它在 QEMU 外面包了一层图形界面,免费开源,而且专门针对 Apple Silicon 做了优化,支持 virtio 加速和 Apple Virtualization 框架(不是 TCG 模拟,而是硬件加速虚拟化)。UI 上手难度介于 Parallels 和裸 QEMU 之间,实际体验已经接近 Parallels 的流畅度。
选 UTM 的另一个理由是它的虚拟化引擎默认使用 Apple Virtualization.framework,这跟 x86 上常见的 Hypervisor.framework 类似,直接把虚拟机的 ARM64 指令交给 CPU 原生执行,速度表现很好。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析:ARM 版 CentOS 7 系统准备
2.1 分清 aarch64 和 armhfp,别下错镜像
这是最容易踩坑的地方,也是整个流程里第一个容易卡死的地方。CentOS 7 对 ARM 架构的支持分为两个分支:aarch64 是 64 位 ARMv8-A 架构的版本,主要跑在鲲鹏、飞腾、HP Moonshot 这类企业级 ARM 服务器上;armhfp 是 32 位 ARMv7-A 架构的版本,主要跑在树莓派 2/3、BeagleBone 这一类开发板上。
M1 芯片是 64 位 ARM 架构,支持 ARMv8-A 指令集,所以必须选择 aarch64 版本。如果你不小心下载了 armhfp 的镜像,放进 UTM 里,启动时大概率直接提示“无法启动”或“没有找到可引导的设备”,因为架构不匹配。
下载镜像的渠道我整理一下。CentOS 官方其实已经停止提供 ARM 版下载了,需要去 CentOS 的 vault 镜像里找,用下面的地址可以直接拉到 Generic 版内核:
http://mirror.centos.org/altarch/7/isos/aarch64/CentOS-7-aarch64-Generic-2009.qcow2.xz
这里我顺便提醒一下,这个镜像只有 1GB 左右,不是完整安装版,而是一个磁盘镜像,启动后进入的是精简文本模式安装器。装完以后是一个纯净的 CentOS 7 系统,你需要自己去配网络、配 Yum,然后安装 JDK。这是 ARM 版和 x86 版完全不同的地方——x86 版有 DVD ISO 可以选图形安装界面,ARM 版没有这个选项。
2.2 UTM 创建虚拟机:参数配置全记录
在 UTM 里创建虚拟机的操作我具体说一下。
打开 UTM,点击新建虚拟机窗口,选择“虚拟化”(注意不是“模拟”),这一步非常关键。虚拟化走的是 Apple Virtualization 框架,是原生性能;模拟走的是 QEMU 的 TCG 翻译模式,性能会相差好几倍。操作系统类型选“Linux”,架构里选“ARM64(aarch64)”。在系统设置里,内存建议给 4096MB 以上,CPU 核心数量给 4 个,这样编译 Java 项目不会太吃力。
存储方面,导入我们刚才下载的 Generic 镜像时,UTM 会自动创建一个空磁盘。不要用那个空盘,直接选择“导入磁盘镜像”,把你下载解压出来的 CentOS-7-aarch64-Generic-2009.qcow2 文件挂载进去。如果用默认的空磁盘,开机后会提示找不到系统。
网络设置选择“bridged”或“shared”模式都可以,建议选择 shared NAT 模式。这个模式下虚拟机能上网,也不会跟宿主机抢 IP 段,后续 yum 安装 JDK 的时候网络会通畅很多。
启动虚拟机后,会进入一个安装引导界面,选择安装 CentOS 7,然后按照提示设置 root 密码和分区策略。整个过程不用图形界面,全是文本向导,跟装云服务器的过程差不多。
2.3 系统启动后的基础网络配置
ARM 版 CentOS 7 启动后,很大概率会发现网络没起来。这是因为 CentOS 7 默认的 NetworkManager 不会自动启用网卡,特别是虚拟机里经常出现只有回环地址、没有外网 IP 的情况。
排查方式很简单,敲 ip addr 看输出。如果 eth0 显示 DOWN 或者根本没出现在列表里,需要在 /etc/sysconfig/network-scripts/ 目录下找到 ifcfg-eth0 文件,把 ONBOOT=no 改成 ONBOOT=yes,然后执行 systemctl restart network 重启网络。
这里多说一句,很多人在这篇文章介绍之前就在这一步放弃了,因为重启网络时报“Failed to restart network.service: Unit network.service not found”的错误。这个我后面会专门讲,不是网卡问题,是服务名变了。
网络通了之后,第一件事是验证 DNS 能不能解析,ping mirrors.aliyun.com 一下看看有没有返回。如果解析不了,修改 /etc/resolv.conf 添加 nameserver 8.8.8.8,或者检查一下 NAT 模式下虚拟机的路由配置。
2.4 替换 Yum 源:官方仓库已经进 vault 了
CentOS 7 在 2024 年 6 月已经停止维护了,官方 mirrors 上的仓库文件基本都被移到了 vault 目录。这意味着你直接 yum install 会报 404 错误。速度慢不说,根本找不到包。
解决方法是替换成阿里云或清华的镜像源。现在国内云服务器上大多数还在跑 CentOS 7,所以阿里云保留了完整的 mirror。操作时直接把 /etc/yum.repos.d/CentOS-Base.repo 里的 baseurl 改成 vault 指向阿里云的路径。这里给出我验证过的写法:
bash复制cd /etc/yum.repos.d/
cp CentOS-Base.repo CentOS-Base.repo.bak
sed -i 's/mirrorlist=/#mirrorlist=/g' CentOS-Base.repo
sed -i 's|#baseurl=http://mirror.centos.org/centos|baseurl=http://mirrors.aliyun.com/centos-vault/7.9.2009|g' CentOS-Base.repo
注意,阿里云的路径是 centos-vault/7.9.2009,不是 centos/7,这两个目录的含义不一样。centos/7 是跟随最新小版本的软链接,vault 是归档版本。改完之后执行 yum clean all && yum makecache 刷新缓存。
2.5 架构验证命令
在安装 JDK 之前,最好确认一下当前系统的架构确实是 aarch64,避免后面下载的 JDK 包架构不匹配。我用的验证命令组合分三条。
bash复制uname -m
cat /etc/redhat-release
getconf LONG_BIT
如果 uname -m 输出的是 aarch64,说明系统架构正确。如果输出 armv7l 或者 armv8l,说明你的镜像下载的是 armhfp 版本,立马换镜像重装。getconf LONG_BIT 输出 64 说明是 64 位系统。
这一步虽然简单,但真的建议每次新建虚拟机都做一下,尤其是从网盘下载的镜像,你根本不知道经手人是否改过文件。
3. 实操过程与核心环节实现:JDK 安装全流程
3.1 JDK 版本选择
JDK 版本选择上,CentOS 7 + ARM 架构的组合有点尴尬。Oracle 官方的 JDK 8 虽然还有 ARM 版本,但下载页面上对 macOS 的 ARM 支持和 Linux aarch64 的支持是分开的,而且 Oracle 版本对商用有限制,咱们自己开发调试就没必要给自己找麻烦。推荐直接用开源的 OpenJDK 或 Eclipse Temurin(Adoptium 社区发行版)。
版本选多少?CentOS 7 的老项目大多在 JDK 8 和 JDK 11 上,如果你接触的是新项目,直接装 JDK 17 或者 JDK 21 也可以。JDK 8 的好处是兼容性强,Java EE 老项目随便跑;JDK 17 的优势是 LTS 版本,支持到 2029 年,新特性多,Spring Boot 3 也强制要求 JDK 17 以上。我在本机测试环境装的 JDK 17,因为当前项目组微服务大多是 JDK 17。
3.2 下载 aarch64 版的 JDK 包
Temurin 的下载地址比较有规律,建议直接在终端用 wget 下载,而不是先下载到 Mac 再拷贝进虚拟机。直接在虚拟机里下载的好处是省去文件传输一步,也避免了宿主机和虚拟机之间共享目录权限问题。
bash复制wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8%2B7/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.8_7.tar.gz
如果你网速不行,也可以去清华 tuna 镜像站下载,速度会快很多,而且不容易超时:
bash复制wget https://mirrors.tuna.tsinghua.edu.cn/Adoptium/17/jdk/aarch64/linux/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.8_7.tar.gz
注意这个下载路径里的 aarch64 字样,看清楚再下。我见过有人把 x64 的包下载下来,放到 ARM 机器上解压,然后 java -version 直接报“cannot execute binary file: Exec format error”,这就是架构不匹配的典型特征。
3.3 解压与路径规划
把 JDK 安装到 /usr/local/java 目录,这是 Linux 上比较常见的软件安装路径,单独创建一个 java 目录避免和其他软件混在一起。
bash复制mkdir -p /usr/local/java
tar -zxvf OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.8_7.tar.gz -C /usr/local/java
cd /usr/local/java && mv jdk-17.0.8+7 jdk17
解压后重命名为 jdk17,目的是把带版本号的目录简化,后续配置 JAVA_HOME 的时候不用频繁改路径。这一步不做也不影响运行,但相信我,以后升级 JDK 或者切换多个 JDK 版本的时候,一个干净整洁的目录结构能省很多事。
3.4 环境变量配置:全局配置 vs 用户配置
JDK 装完之后,要让系统找到 java 命令,需要配置环境变量。这里有两种做法:改全局的 /etc/profile 或者改当前用户的 ~/.bashrc。
全局配置适合一台机器只服务一个开发用户的场景,改了之后所有用户都能用。~/bashrc 配置只对当前用户生效,适合多用户共享虚拟机的场景。我这里用的是 /etc/profile 配置,因为虚拟机上就我一个人用,配置全局放那儿省心。
用 vim 编辑 /etc/profile,在文件末尾追加以下内容:
bash复制export JAVA_HOME=/usr/local/java/jdk17
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
这里有个细节值得展开讲一下。JAVA_HOME 是给 Maven、Gradle 和 Tomcat 这些 Java 工具链用的,它们靠这个环境变量定位 JDK 安装位置。PATH 是让 shell 能直接找到 java 和 javac 可执行文件。CLASSPATH 在 JDK 9 之后其实不是必须的了,因为模块化之后 JDK 会自己加载核心类库,但老项目可能还需要,所以一并写上,兼容性最好。
保存退出后执行 source /etc/profile 让配置立即生效。之前我见过有人改完 /etc/profile 不执行 source 就直接敲 java -version,结果提示找不到命令,这其实不是配置没写对,是环境变量还没有加载。
3.5 验证安装结果
完成上面的步骤后,用下面几个命令做一轮全面验证,确认 JDK 真的装好了。
bash复制java -version
javac -version
echo $JAVA_HOME
which java
输出结果应该类似于:
bash复制openjdk version "17.0.8" 2023-07-18
OpenJDK Runtime Environment Temurin-17.0.8+7 (build 17.0.8+7)
OpenJDK 64-Bit Server VM Temurin-17.0.8+7 (build 17.0.8+7, mixed mode, sharing)
到这里 JDK 安装已经是成功的了。如果 java -version 能输出版本但 javac -version 报错,说明你只把 JRE 的 bin 加到了 PATH,而不是 JDK 的 bin。做开发必须用 JDK,光有 JRE 跑不了编译任务。
3.6 可选加固:软链接方式
最后介绍一个小技巧,很多云服务器上的默认 Java 配置都用了这种方式。做软链接的好处是,不依赖环境变量也能直接执行 java 命令。
bash复制ln -s /usr/local/java/jdk17/bin/java /usr/bin/java
ln -s /usr/local/java/jdk17/bin/javac /usr/bin/javac
这样做的好处是,系统自带的脚本(比如某些服务启动脚本)会去找 /usr/bin/java,而不是去读 JAVA_HOME。如果没有软链接,那些脚本会提示找不到 java。但要注意,做了软链接也不能省掉 /etc/profile 里的配置,因为 Tomcat 等应用仍然需要 JAVA_HOME 环境变量来定位 JVM 的安装路径。
4. 常见问题与排查技巧实录
4.1 安装过程中的典型报错与解决办法
这部分的坑我觉得是最有参考价值的,都是实际试出来而不是看文档想象出来的。
第一个坑是“Unit network.service not found”。执行 systemctl restart network 的时候报这个错,不是网卡配置的问题,而是 CentOS 7 中 network 服务被 NetworkManager 取代了。解决办法是执行 nmcli networking off && nmcli networking on,或者直接用 systemctl restart NetworkManager 重启网络管理服务。
第二个坑是 yum makecache 报“Could not resolve host: mirrors.aliyun.com”。这个要先排查 DNS 配置文件。在 /etc/resolv.conf 中加一行 nameserver 223.5.5.5(阿里 DNS)或者 8.8.8.8,然后重试。如果是新装的系统,有时候 resolv.conf 是空的。
第三个坑是 java -version 提示“cannot execute binary file”。这个十有八九下载错了架构。在 ARM 系统上执行 x86 的二进制文件不会有正常的报错提示,系统只会简单回你一句“格式错误”。用 file 命令查看一下下载的 JDK 包架构:
bash复制file /usr/local/java/jdk17/bin/java
输出应该是 ELF 64-bit LSB executable, ARM aarch64,如果显示 x86-64,那就直接换包重下。
第四个坑是安装完 JDK 后 Tomcat 启动闪退。这种情况往往不是 JDK 的问题,而是新装的系统时间不对,导致 JVM 启动时的证书验证失败。解决办法是安装 chrony 或者手动同步时间:
bash复制yum install -y chrony
systemctl start chronyd
systemctl enable chronyd
4.2 常见问题速查表
| 现象 | 可能原因 | 排查/解决步骤 |
|---|---|---|
| 启动虚拟机看不到引导界面 | 镜像架构不对或没有导入磁盘 | 确认是 aarch64 的 qcow2 镜像,UTM 中选择导入磁盘镜像 |
| 开机后没有 IP 地址 | 网卡没有自动启用 | 修改 ifcfg-eth0 的 ONBOOT=yes,重启 NetworkManager |
| yum 报 404 | 官方源迁移到 vault | 替换 baseurl 为 mirrors.aliyun.com/centos-vault/7.9.2009 |
| java 命令找不到 | 环境变量没生效 | 执行 source /etc/profile 后重试 |
| java -version 报 Exec format error | 下载了 x86 的 JDK | file 命令检查二进制格式,重新下载 aarch64 版本 |
| javac 找不到 | 只装了 JRE | 确认下载的是包含编译器的 JDK 包而非 JRE |
| Tomcat 启动即退出 | 系统时间不对 | 安装 chrony 同步时间 |
| wget 下载 github 慢 | 网络问题 | 换清华 tuna 镜像下载 |
| 这台虚拟机上跑 Java 程序内存不够 | 分配的 RAM 太少 | UTM 设置里提高内存到 4GB 或以上 |
4.3 关于 CentOS 7 生命周期的重要提醒
另外必须提醒一句,CentOS 7 官方在 2024 年 6 月 30 日停止维护了,这意味着这个系统不会再收到任何安全更新和 bug 修复。如果你只是本地做开发或者学习用,问题不大,反正你用的是 aarch64 的虚拟机,出问题重装就完事了。但如果是要部署生产环境,我建议认真评估升级到 CentOS Stream、Rocky Linux 或 AlmaLinux 的必要性,别为了习惯把自己绑在一个没有任何安全补丁的系统里。
4.4 一个提高效率的额外技巧
在 M1 Mac 上配合 UTM 使用的时候,我推荐开启 UTM 的共享目录功能。在虚拟机的配置中找到“共享目录”选项,把 Mac 上的某个文件夹(比如 ~/workspace)映射到虚拟机的 /mnt/host 路径。这样你可以在 Mac 上写代码,在虚拟机里编译运行,文件传输通过共享目录实时同步,不用来回 scp 拉文件。实测在 M1 上这个速度很快,基本没有感知延迟。
有了这个配合,日常用 vscode 远程连进虚拟机写代码也行,直接用 UTM 图形界面里的终端也行,选择就灵活多了。
5. 最后的实操总结与我的体会
折腾完这一整套环境,我自己的整体感受是:M1 Mac 上跑 ARM 版 CentOS 7 完全可用,只要别用错镜像和 JDK 架构,安装速度(差不多 40 分钟能搞定,如果网络快的话)和运行体验跟一台真正的 ARM 服务器没有太大区别。ARM 版 CentOS 7 本身的软件生态没有 x86 丰富,但解决 JDK 安装、数据库、中间件这些常规开发需求是绰绰有余的。
实际操作中我还发现一个小技巧,值得单独提一句。VM 里安装 JDK 之后,最好在 /etc/profile.d/ 目录下创建一个 java.sh 文件,专门放 Java 环境变量。
bash复制vim /etc/profile.d/java.sh
把之前的 export 配置写进去,这样比直接改 /etc/profile 更模块化,以后卸载或切换 JDK 版本时,删掉这个文件就行,不会污染全局配置。这也是很多发行版推荐的做法。
最后再分享一个排查技巧。如果你在 ARM 版 CentOS 7 上碰到编译报错,比如“/bin/ld: 找不到 -ljava”,大概率是 JDK 的链接库路径没有配置。这种情况下在 /etc/profile 里加上:
bash复制export LD_LIBRARY_PATH=$JAVA_HOME/lib/server:$LD_LIBRARY_PATH
这个路径下有 libjvm.so 和 libjava.so,加完再 source 一下一般就能通过链接。
这套环境最大的价值在于,它跟云上跑 ARM 架构服务器的路径完全一致。无论你在国内某云厂商买的鲲鹏实例,还是在海外云服务商买的 Graviton 实例,它们跑的操作系统都是 aarch64 版 Linux,JDK 也是同一个 aarch64 包。本地调试完放上去就能跑,能省掉相当多的环境适配时间。希望这篇文章能帮你少走些弯路。
