简介在ARM架构的Linux系统如树莓派、飞腾、鲲鹏等aarch64设备上运行或开发Java应用需要配套的JDK。这份JDK 11.0.20正是面向此类场景的Linux aarch64版本压缩包包含编译、运行、调试Java程序的完整工具链适合需要在ARM设备上构建并部署项目的开发者。压缩包共392个文件总大小约158.16MB除核心的java、javac、jmod模块文件外还包含类库、动态链接库so、安全策略配置及jcmd、jstat、jarsigner等命令行工具覆盖从日常开发到性能调优的常见需求。该版本属于长期支持LTS版本可享受持续的安全更新与维护适合生产环境使用。资源目前已有697人学习下载文件目录结构清晰解压并配置环境变量后即可直接使用。1. ARM机器上的Java 11不是玄学先搞清楚aarch64包与x86包的区别在ARM服务器或开发板上部署Java应用最常见的翻车不是版本选错而是随手拿了个Linux x86_64的JDK硬解压。执行java -version弹出cannot execute binary file时多半是架构不匹配不是包坏了。jdk-11.0.20-linux-aarch64-bin.tar就是针对ARM 64位Linux的JDK 11二进制包它自带aarch64架构的JVM能直接跑在各类ARM开发板和ARM服务器上解决Java服务起不来、工具脚本不能用这类具体问题。这篇文章从选型、安装、排错到用jlink裁减运行时一次讲完新手能照着敲熟手也能看到边界参数和实际踩过的坑。2. aarch64与x86_64的Java运行区别为什么ARM机器必须用专用JDK包2.1 架构不匹配的直接后果cannot execute binary file的底层原因Java应用编译出来的.class字节码确实是跨平台的这一点没有争议。但真正把字节码跑起来的JVM本身是一个用C/C编译出来的原生可执行文件。它编译成哪类CPU的指令集就只能在哪类CPU上执行。x86_64的JVM执行的是x86指令集ARM64的CPU译码器不认识这些指令操作系统加载这个ELF可执行文件时会根据ELF头里的e_machine字段判断它能否在当前内核上运行x86_64对应的值是62aarch64对应的是183内核一比对发现不匹配就拒绝加载shell于是抛出cannot execute binary file。这也是为什么在ARM机器上装JDK必须先看清包名里的架构标识。jdk-11.0.20-linux-aarch64-bin.tar里的aarch64表示ARM 64位指令集它是和JVM二进制强绑定的。要注意的是一部分ARM设备还运行着32位用户态比如armv7l架构对应的是armhf包如果设备是32位用户态装aarch64包一样跑不起来。在动任何安装操作之前先用下面两条命令确认硬件状态uname -m getconf LONG_BIT第一条输出aarch64说明内核是64位ARM架构第二条输出64说明用户态也是64位。两个条件都满足再解压JDK。uname -m在大多数ARM服务器上输出aarch64这是最干净的标识但有些开发板在32位用户态下输出armv8l这时你需要的不是aarch64包而是armhf包。这个坑我踩过不止一次当时拿着aarch64的JDK往板子上传解压后怎么执行都报错最后用file命令检查二进制才反应过来。2.2 读懂JDK版本命名11.0.20、linux-aarch64-bin.tar的每个字段先完整拆一下文件名。jdk-11.0.20-linux-aarch64-bin.tar从左到右是四段信息。第一段11.0.20主版本11这是Java SE的LTS长期支持版本补丁更新按季度发布包含安全修复和少量bug修复11.0.20属于11生命周期内较新的季度更新适合生产环境。第二段linux目标操作系统是Linux。第三段aarch64目标CPU架构是ARM 64位对应ARMv8-A指令集这一字段决定了JVM能不能被加载。第四段bin.tarbin表示编译好的二进制产物tar表示用tar归档格式打包GNU tar的-xf参数可以自动识别gzip、xz等压缩格式所以文件名里即使没有.gz或.xz后缀解压命令也不受影响。实际选型时还要区分开发包和运行包。JDK是完整开发工具集包含javac、jlink、jdeps、jshell等工具JRE是纯运行环境。Java 11之后官方不再单独发布JRE安装包但这不代表你只能背着一个庞大的JDK到处部署。第5章会演示怎么用jlink从JDK里裁出一个只含必要模块的精简运行时这在ARM嵌入式设备上尤其实用。选JDK版本本身也是一个需要确认的决策。如果你的应用依赖较老的三方库或者用了部分只在Java 8下正常的反射写法直接从8跳到11可能会遇到模块化或IllegalAccessError这类问题。反过来如果你是新项目建议至少在11和17之间选一个LTS版本。11.0.20这个包的价值在于它在Java 11的兼容性基础上集成了截至该版本的季度安全修复同时aarch64的JVM实现相对成熟是很多ARM服务器和容器镜像里常用的基础运行环境。2.3 下载源的信任边界哈希校验与文件真实性从网上下载JDK包习惯上要先确认文件完整性和来源可靠性。最常见也最有效的验证手段是计算SHA-256哈希和发布方给的值比对。这一步能同时发现两个问题下载过程中文件损坏、以及文件被替换或篡改。命令非常简单sha256sum jdk-11.0.20-linux-aarch64-bin.tar输出是一个64位的十六进制字符串只要它和发布方提供的官方哈希一致基本可以确定文件完整且未被改动。严格一点的场景还会验GPG签名用发布方的公钥验证.sig文件但这需要额外维护密钥信任链对多数内部部署来说成本偏高。我自己的习惯是生产环境要求至少做一次SHA-256比对测试环境就算跳过也要保证压缩包是从可信来源直接下载的。这里有一个容易踩的细节同一个版本号、同一个架构不同构建商的产物哈希值各不相同。你下载的是哪一家的构建就要以那一家的哈希值为准。网上随手搜来的哈希只能作为参考不能作为验证依据否则可能拿一个错误或伪造的哈希去比对反而放过了真实问题。2.4 选型决策通用OpenJDK构建、系统包管理器、厂商定制版怎么选在ARM Linux上装Java 11除了直接下载二进制tar包还有另外两条常见路径各自有明确的使用边界。第一条是系统包管理器安装。部分Linux发行版通过apt或yum提供openjdk-11相关包好处是自动解决依赖关系、更新也方便坏处是版本滞后发行版仓库里的JDK版本往往落后于官方季度更新而且安装位置、目录结构被系统策略固化不好自己控制。第二条是芯片厂商或整机厂商提供的定制JVM这类版本针对特定ARM平台做过指令集优化适合跑重型计算或对性能有极端要求的场景但通常需要单独申请、授权链条也长普通业务用不上。放在一起对比适用场景就很清楚了选型适合场景注意点通用OpenJDK二进制包生产环境、版本敏感的Java服务自包含、版本精确依赖自己管理系统包管理器安装测试环境、快速搭建版本滞后、目录受控厂商定制版高性能计算、业务强绑定授权与维护成本高实际部署时我的选型规则很固定生产环境一律用自包含的二进制包安装到统一目录不走系统包管理器同一台机器有多版本JDK共存的需求再用update-alternatives管理。这样可以保证版本可控、回滚方便也避免系统包更新时悄悄改掉你的默认JDK。3. 部署JDK 11解压、环境变量与多版本共存的完整操作3.1 安装前检查与解压把JDK放进/opt并核对目录布局进入安装环节先把机器状态确认完。除了前面说的uname -m和getconf LONG_BIT还要看磁盘空间和解压目录权限。JDK 11解压后占用空间不小运行期还会产生缓存和日志建议至少留出1GB以上的可用空间。我一般把JDK统一安装到/opt/java下不使用发行版默认的/usr/lib/jvm目录原因是避免和系统自带的JVM互相干扰也方便后续做多版本管理。sudo mkdir -p /opt/java sudo tar -xf jdk-11.0.20-linux-aarch64-bin.tar -C /opt/java ls -l /opt/javatar -xf拆开看-x是解压-f指定归档文件-C /opt/java把解压目标目录指到/opt/java。不少打包文件内部会自带一层顶层目录解压后生成/opt/java/jdk-11.0.20-linux-aarch64这样的完整路径。所以正确用法是先把目标目录建好、再在目标目录上一级执行tar -xf避免把一堆文件和目录直接铺散到当前路径。解压完成后ls -l应能看到bin、lib、jmods、conf这几个标准子目录。这里补一个ARM设备上的使用习惯解压大包时不要加-v参数。tar -xvf会把每个解压出的文件名刷到终端上在ARM开发板这类I/O性能一般的设备上几万个文件名的输出本身就足以让安装过程慢好几倍。需要确认归档内容时用下面的方式更快sudo tar -tf jdk-11.0.20-linux-aarch64-bin.tar | head-t是列出归档内容的模式不真正解压配合head只看前几行确认目录结构是否正常就够了。在磁盘空间紧张或I/O受限的环境里这个习惯能省不少时间。3.2 设置JAVA_HOME和PATH当前会话与永久配置的差别解压完成只是第一步当前shell还不会自动找到java命令需要配置环境变量。最精简的两行export JAVA_HOME/opt/java/jdk-11.0.20-linux-aarch64 export PATH$JAVA_HOME/bin:$PATH第一行定义JAVA_HOME指向JDK的安装根目录是很多脚本和应用读取Java路径的依据第二行把$JAVA_HOME/bin插入到PATH的最前面这样执行java时优先命中我们自己装的版本。直接敲这两行只对当前终端会话有效一关终端就失效。要让配置永久生效需要写进shell的启动脚本。多数Linux发行版默认shell是bash配置写入~/.bashrcecho export JAVA_HOME/opt/java/jdk-11.0.20-linux-aarch64 ~/.bashrc echo export PATH$JAVA_HOME/bin:$PATH ~/.bashrc source ~/.bashrc如果默认shell是zsh写入~/.zshrc如果这台机器要面向多个账号统一配置则写到/etc/profile.d/java.sh这个目录下的脚本会在用户登录时自动加载比手动维护每个账号更省事sudo tee /etc/profile.d/java.sh EOF export JAVA_HOME/opt/java/jdk-11.0.20-linux-aarch64 export PATH$JAVA_HOME/bin:$PATH EOF注意两个细节。第一JAVA_HOME里不要带尾斜杠很多脚本拼接$JAVA_HOME/bin时对多一个斜杠的处理不一致少写一个能少踩一类路径怪的坑。第二/etc/profile.d/的配置对非交互式登录不一定生效如果你通过某些远程执行工具跑命令可能需要额外source或直接在服务脚本里写环境变量。3.3 验证安装java、javac、jshell三样检查清单环境变量配置好并source当前shell之后依次跑三条命令做基础验证java -version javac -version jshell --version正常输出里应该能看到openjdk version 11.0.20和OpenJDK 64-Bit Server VM之类的描述版本号明确写有11.0.20。重点确认三件事版本号是不是11.0.20、JVM是不是64位、当前平台是不是aarch64。这里有一个容易误会的细节java -version的输出是打到标准错误流stderr的不是标准输出stdout。如果你在脚本里用$(java -version)去捕获输出经常什么都拿不到这是Java长期以来的行为不是你的安装有问题。再验证一下命令路径是否真的指向了我们安装的JDK避免被发行版自带的旧Java抢先type -a java readlink -f $(which java)type -a java列出所有出现在PATH里的java可执行项readlink -f解析软链接的最终路径结果应以/opt/java/jdk-11.0.20-linux-aarch64/bin/java结尾。这一步很关键因为有不少ARM开发板镜像预装了Java你的PATH如果设置顺序不对which java仍然指向旧版本造成“配置了JAVA_HOME却不生效”的错觉。3.4 多版本共存update-alternatives的优先级与手动软链生产机器上经常要同时保留JDK 8和JDK 11用于跑不同的遗留应用。在多数使用dpkg机制的Linux发行版上update-alternatives是管理命令版本冲突的内置工具它通过维护一组符号链接来切换当前生效的JDK。注册JDK 11到系统中sudo update-alternatives --install /usr/bin/java java /opt/java/jdk-11.0.20-linux-aarch64/bin/java 1100 sudo update-alternatives --install /usr/bin/javac javac /opt/java/jdk-11.0.20-linux-aarch64/bin/javac 1100 sudo update-alternatives --config java--install后面跟着四段参数第一个是符号链接要放的位置/opt/java/jdk-11.0.20-linux-aarch64/bin/java是实际程序路径1100是优先级数字数值越大在自动模式下越优先被选中。java和javac是两个独立的命令必须分别注册这一步很多人会漏掉javac结果java -version正常但javac编译时仍指向旧版。执行--config java时会进入一个交互式选择界面按对应的数字键切换当前生效的JDK。如果不想要交互也可以直接用--set指定sudo update-alternatives --set java /opt/java/jdk-11.0.20-linux-aarch64/bin/java与手动创建软链接相比update-alternatives的优势在于它把链接的变更记录写入系统配置文件装新包或卸载包时系统能识别并正确处理不会发生“自己改的软链被包管理器悄悄覆盖”的尴尬。运行RPM类发行版的环境则通常用alternatives命令或者直接在/usr/bin下面做软链机制类似但命令名不同。如果没有多版本需求只装一个JDK那么前面配置好JAVA_HOME和PATH就够了不需要引入alternatives。4. aarch64 JDK 11运行排查五个常见坑与修复方法4.1 启动报错cannot execute binary file本质是内核拒绝加载现象在ARM机器上解压完JDK后执行java -versionshell直接报cannot execute binary file: Exec format error。原因这是典型的架构不匹配。JVM二进制是Linux可执行文件但它的ELF头里声明的目标架构和当前内核不匹配。另一个常见场景是在ARM机器上解压了一个x86_64的包解压过程正常、目录结构完好但执行时内核直接拒绝加载。解决先确认机器真实架构再用file命令检查二进制实际格式uname -m file /opt/java/jdk-11.0.20-linux-aarch64/bin/java如果file输出里带ARM aarch64而java仍不能执行检查包是否解压完整或文件是否有执行权限如果输出显示x86-64说明下载错了包需要换成aarch64版本、重新校验哈希后再次解压。这个坑看起来初级实际出现的频率不低不少情况下是拿以前下载的x86包应急存储、时间一久自己也记不清了。4.2 提示error while loading shared librariesLD_LIBRARY_PATH弄乱了现象java文件权限和架构都正常但启动时报error while loading shared libraries: libjli.so: cannot open shared object file或者提示缺libjvm.so。原因JDK的正常运行依赖安装目录下lib和lib/server里的多个本地库。这些库默认通过编译进二进制的rpath自动查找。但如果系统环境变量LD_LIBRARY_PATH里配置了指向其他目录的内容动态加载器会优先搜索这个环境变量指定的路径找不到时就报错。还有一种情况是你手动加了LD_LIBRARY_PATH但漏掉了lib/server这个子目录而lib/server/libjvm.so正好是JVM本体所在。解决先看动态库依赖的实际解析情况ldd $JAVA_HOME/bin/java正常的输出里大部分so文件都应有对应路径不会全部是not found。如果libjli.so或libjvm.so指向了不存在的路径最稳妥的做法是把自定义的LD_LIBRARY_PATH清理掉让JDK自带的rpath生效。如果确实要为其他软件保留这个环境变量就把它改为既包含$JAVA_HOME/lib、也包含$JAVA_HOME/lib/server的完整配置注意路径顺序、不要覆盖JDK自身的查找。排查这个问题时strace -e openat可以看到动态加载器实际打开了哪些路径但多数情况下先查环境变量就能定位。4.3 启动Java进程后直接被Killed堆内存超了ARM设备限额现象java -jar app.jar跑几秒后进程消失没有任何异常堆栈。查看系统日志显示Out of memory或者进程被内核OOM killer杀掉。原因ARM开发板和部分入门级ARM服务器的内存通常只有1GB到4GB。JVM在没有显式配置时默认最大堆按物理内存的四分之一计算在1GB的机器上默认堆就有256MB再算上Metaspace、线程栈、JIT编译缓存进程峰值很容易就冲破设备物理内存上限。更麻烦的是很多ARM设备还启用了cgroup限制JVM直接探测宿主内存实际可用配额远小于默认值启动后立刻被杀。解决显式限制堆和Metaspace的大小java -Xmx256m -XX:MaxMetaspaceSize128m -jar app.jar-Xmx指定最大堆内存-XX:MaxMetaspaceSize限制JVM元数据区域的上限。如果256MB还不够用需要确认应用本身的内存占用基线再逐档调整。一个简单的经验法则物理内存1GB时JVM的总开销不要超过600MB剩下的要留给操作系统和进程自身。可以用free -m先确认当前可用内存再决定-Xmx和-XX:MaxMetaspaceSize的具体值。这里切忌盲目调大ARM板子上的内存一旦吃满系统可能连SSH都无响应。4.4 容器内Java看到的CPU数不对ActiveProcessorCount与UseContainerSupport现象Java应用部署在Docker容器里限制--cpus2、内存512MB但应用里Runtime.getRuntime().availableProcessors()返回宿主机的16核JVM默认堆也按照宿主物理内存计算直接造成容器频繁被杀。原因JDK 11默认启用了容器支持特性-XX:UseContainerSupport理论上会自动读取cgroup配额。但在内核版本较老、cgroup v1切换cgroup v2的早期阶段或者使用了特殊的pause容器时JVM对容器配额探测经常不准于是回退到读取宿主机的CPU和内存数据。解决在容器启动时显式指定CPU数量和内存比例java -XX:ActiveProcessorCount2 -XX:MaxRAMPercentage50.0 -jar app.jar-XX:ActiveProcessorCount直接覆盖JVM探测到的CPU数量它影响的不仅是availableProcessors()的返回值还包括GC线程数、ForkJoinPool并行度等一系列内部参数。-XX:MaxRAMPercentage50.0让最大堆按容器可用内存的50%计算比手工指定-Xmx更适应动态配额。如果容器平台对cgroup读取一直正常可以不额外设置但如果发现Java在容器里识别的资源明显偏大这两个参数就是最直接的修正手段。4.5 涉及AWT/Swing的代码报字体缺失headless模式的选择现象应用里用到java.awt相关代码比如生成验证码图片或绘制图表部署到ARM设备后启动抛HeadlessException或者报FontConfiguration错误找不到字体文件。原因无头服务器通常没有安装X11、字体库或fontconfig。JDK的图形相关模块初始化时要读取本地字体信息在精简的ARM发行版上这些组件要么没装要么被裁剪掉了。解决如果应用本身不依赖真实显示器最干净的做法是强制开启headless模式java -Djava.awt.headlesstrue -jar app.jar这个参数告诉JVM以无头模式运行即使系统没有显卡和显示器也能使用java.awt里的纯逻辑功能。如果应用确实需要生成含文字的图片光靠headless还不够必须安装系统字体和字体配置。以多数Linux发行版为例用系统的包管理器安装fontconfig和fonts-dejavu-core后重启应用即可。我的处理习惯是部署到ARM服务器上的图形相关代码先确认业务能在headless模式下运行再考虑补字体避免为了一两个图片功能把整个图形环境都装进去。5. 进阶用法用jlink裁出几十MB的ARM精简JDK5.1 用jdeps分析应用依赖模块jlink适合两类场景一是按应用实际用到的模块裁剪JDK体积二是减少ARM设备上不必要的组件、降低攻击面。先用JDK自带的jdeps扫描应用依赖jdeps --print-module-deps --ignore-missing-deps app.jar输出会是一段逗号分隔的模块名称列表例如java.base,java.logging,java.sql。--ignore-missing-deps让非模块化依赖不影响输出结果对常规的Java服务特别友好避免报一堆找不到的模块名干扰判断。5.2 生成精简运行时拿到依赖清单后用jlink生成精简运行时jlink --module-path $JAVA_HOME/jmods \ --add-modules java.base,java.logging,java.sql,java.naming \ --strip-debug --no-header-files --no-man-pages \ --output /opt/jre11-min--add-modules必须包含java.base这是所有模块的基础--strip-debug去掉调试符号体积更小--no-header-files和--no-man-pages分别去掉C语言头文件和手册页--output指定产物目录要求这个目录事先不存在。生成完成后用/opt/jre11-min/bin/java -version验证。5.3 验证与替换把应用的启动脚本指向新的精简运行时实测一遍核心功能特别是涉及JDBC连接、日志输出这些依赖了对应模块的路径。确认无误后可以将JAVA_HOME改为/opt/jre11-min完成运行时替换。注意jlink产物目录结构同样包含bin、lib、conf和完整JDK的布局一致多版本管理时也可以直接接入update-alternatives。从那以后我每次在ARM设备上装JDK都强制走一遍uname -m确认架构、sha256sum校验包、java -version验证版本这三步再决定要不要做jlink精简。这套流程帮我避免过不少半夜排查的尴尬。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?