ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux装JDK部署jar包指南:环境变量、多版本切换与systemd实践

Linux装JDK部署jar包指南:环境变量、多版本切换与systemd实践 这类问题我回答过太多次了几乎每次都有同事或者网友来问Linux上怎么装JDK、部署一个jar包怎么老出错、linux指定jdk运行jar文件到底怎么搞、为什么同一个机器上好几个Java项目用的版本还不一样。这篇就把这些事一次讲透。无论你是零基础刚接触Linux的实习生还是已经用Windows开发了一段时间想把Spring Boot应用扔到云服务器上的Java程序员照着下面这套流程走基本不会再被Java环境这关卡住。整篇内容都围绕三个关键词展开linux、jdk、jar包。装好JDK是第一步环境变量配好是第二步真正把jar包部署并跑起来是第三步中间我会把那些踩过的坑、排查思路、常用命令一起带上保证你读完既能动手操作也能理解背后的原理。1. 装JDK之前先把版本和发行版选择这件事弄明白很多新手一上来就问JDK怎么下载但真正的问题是你要下载哪个JDK。这里牵扯到两个选择一是OpenJDK和Oracle JDK的区别二是版本号选哪个。如果你只是部署项目不想惹麻烦直接选OpenJDK的LTSLong Term Support版本就对了比如JDK 8、JDK 11、JDK 17。这仨是目前生产环境里最常见的Spring Boot之类的框架对它们的兼容性最好。1.1 OpenJDK与Oracle JDK怎么选在Linux服务器上大多数人用的是OpenJDK因为它是开源免费发行的很多Linux发行版的软件源里直接就有一条命令就能装上。Oracle JDK也不是不能装只是它对商用场景有限制条款而且你不一定需要那些企业级特性。对绝大多数部署jar包的个人项目和中小团队项目来说OpenJDK完全够用性能差距在常规业务场景下几乎感知不到。我之前有个同事非要去Oracle官网下载JDK结果被那个繁琐的登录流程和页面跳转搞到怀疑人生最后老老实实换成了OpenJDK。我的建议是不是被迫做特定兼容性调试就选OpenJDK省时省力。另外现在网上能搜到很多jdk镜像网站个人使用的话确实方便但生产环境我还是倾向于用官方源或者Linux发行版自带的软件源至少安全和维护性有保障。1.2 LTS版本号怎么定版本号的选择直接去看你项目的依赖要求。Spring Boot 2.x老项目常见搭配是JDK 8JDK 11也有一大批用户Spring Boot 3.x和较新的框架则普遍要求JDK 17起步。如果你完全从零开始没有历史包袱我个人推荐装JDK 17因为它在性能、GC垃圾回收行为、容器支持方面都有明显提升而且新框架支持得最好。这里有个血泪教训不要因为追求新特性就去装JDK 21或者更新的非LTS版本如果你部署的jar包是用旧版本编译的运行时没准就会冒出一些诡异的字节码版本错误排查起来特别浪费时间。生产环境求稳版本号对齐开发环境这是铁律。2. 开始动手安装前必须做的环境检查在真正安装之前先花两分钟做环境检查能帮你避开后面90%的怎么装完还是不能用问题。这一步对应的就是常说的linux常用命令很多面试题里也会考。2.1 确认你的Linux发行版和系统架构不同发行版用了不同的包管理器安装命令不一样所以第一步是识别系统。cat /etc/os-release uname -acat /etc/os-release会显示发行版名称和版本号比如Ubuntu、CentOS、Debian、Rocky Linux。uname -a会显示内核信息里面能看到架构类型通常是x86_64或者aarch64ARM架构。下载JDK时一定要选对架构。同一个JDK版本会有x64和ARM 64-bit这类不同压缩包在ARM服务器上强行装x64版本效果等同废纸命令虽然能执行但报错信息会非常迷惑。2.2 用命令随手查一下系统里有没有残留的Java很多服务器不是全新买的上一任同事或者某个安装脚本可能已经装过Java。如果你不管三七二十一直接装很容易出现我明明装了新版本java -version还是显示老版本的灵异事件。which java java -version echo $JAVA_HOME如果哪一条命令有输出说明系统里已有Java环境。这时候不要急着卸载先搞清楚它是怎么装的。如果是通过apt/yum装的卸载干净再重装如果是手动解压的把对应的环境变量配置注释掉就行。后面的多版本JDK切换章节里我会给出更细的办法这里先有一个必须检查的意识就够了。3. JDK安装实操手动解压方式最灵活yum/apt方式最省心安装JDK大概有三条路软件源安装、手动解压、容器镜像封装。对绝大多数部署场景来说前两条就够了。下面分别把流程写一遍你把对应的命令复制过去微调一下就能用。3.1 软件源安装Ubuntu/Debian、CentOS/RHEL系这是最省心的方式。Ubuntu/Debian用aptCentOS/RHEL/Rocky用yum或dnf。以Ubuntu为例sudo apt update sudo apt install openjdk-17-jdk以CentOS/Rocky为例sudo yum install -y java-17-openjdk装完验证java -version javac -version这种方式的优点是快缺点也同样明显JDK被拆散在系统的各个目录里JAVA_HOME不一定自动帮你设好有些软件只认JAVA_HOME环境变量不认java命令所以你还是得手动把环境变量补上。但至少java这个命令不会报not found了。3.2 手动解压安装推荐特别适合需要指定JDK的场景所谓手动解压安装本质上就是下载官方提供的tar.gz压缩包解压到指定目录然后把PATH和JAVA_HOME指向解压目录。这也是linux指定jdk运行jar文件最依赖的一种安装方式因为它能让你精确控制JDK的位置。步骤一找到下载链接。去OpenJDK官网下载页或者你的发行版镜像站找一个与你系统架构匹配的tar.gz包。下载链接可以先用wget或curl在服务器上直接拉取。mkdir -p /usr/local/java cd /usr/local/java wget https://your-jdk-download-link/jdk-17_linux-x64_bin.tar.gz步骤二解压。tar -zxvf jdk-17_linux-x64_bin.tar.gz解压之后会得到一个文件夹比如jdk-17.0.10。这时候建议给它起一个简短明确的目录名方便后面配置和管理。mv jdk-17.0.10 /usr/local/java/jdk17步骤三配置环境变量。编辑/etc/profile或者~/.bashrc推荐编辑/etc/profile让所有用户都能用sudo vim /etc/profile在文件末尾追加以下内容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然后让配置生效source /etc/profile这个过程中最常见的问题就是jdk环境变量配置失败多数情况是JAVA_HOME路径写错或者PATH里漏了$JAVA_HOME/bin这一层。记住一个小技巧配置完后验证不要只看java -version还要专门验证echo $JAVA_HOME如果这个变量是空的说明你的source没有生效或者你配置去的文件跟你当前的shell不对应。3.3 手动解压和软件源安装在管理上的差异你要是动手实践过两种方式就能感觉到手动解压的可控性是软件源安装没法比的。比如服务器上有好几个项目一个要求JDK 8另一个要求JDK 17手动解压意味着你可以把两个JDK放在两个不同目录里随时通过切换环境变量或指定绝对路径来分别启动。而软件源安装虽然也支持同时存在多个版本但你得依赖update-alternatives来切换新手容易搞乱。4. 多版本JDK共存linux指定jdk运行jar文件的三种做法标题里的linux指定jdk运行jar文件是很多人的真实痛点。机器上明明有多个JDKjava -version显示的是旧的但项目C需要新的总不能反复改全局环境变量吧这里直接上三种可行方案。4.1 启动脚本里显式指定JAVA_HOME最直白、最不容易出错的做法。写一个启动脚本start.sh在脚本内部先指定JAVA_HOME和PATH然后再执行java -jar。这样做的好处是不管全局环境变量是什么脚本内部自成一派。#!/bin/bash export JAVA_HOME/usr/local/java/jdk17 export PATH$JAVA_HOME/bin:$PATH nohup $JAVA_HOME/bin/java -jar myapp.jar --spring.profiles.activeprod app.log 21 注意我在脚本里用的是$JAVA_HOME/bin/java而不是直接写java这是为了避免系统PATH里哪个旧版本的java抢先被执行。这么写的确定性是最高的也是我自己生产环境里最常用的一种方式。4.2 使用update-alternatives切换系统默认JDK如果你的诉求不是单独指定某个项目而是把整个系统的默认JDK改成新版本那用update-alternatives最合适尤其适合Debian系Linux。sudo update-alternatives --config java执行后会列出系统里所有已注册的Java路径输入数字回车即可切换。Java开发相关的其他命令比如javac、jar、keytool也需要分别切换sudo update-alternatives --config javac这个工具的底层就是维护一个符号链接把/usr/bin/java指到你选中的那个JDK路径上。如果哪天发现切换了但没生效记得先hash -r刷新shell的命令缓存或者重新登录会话再试。4.3 用软链接指定某个jar的运行环境如果你不想写脚本也不想改全局配置只想在命令行手动启动一个特殊项目可以用软链接临时指定。思路是建立一个临时目录把需要的java命令软链过去。mkdir -p /opt/jdk17-bin ln -sf /usr/local/java/jdk17/bin/java /opt/jdk17-bin/java /opt/jdk17-bin/java -jar special-app.jar这个方法比较冷门但确实能在一些奇怪的场景下帮上忙比如某个服务脚本写死了只能调用某个路径下的java命令你就可以用软链接去骗过它。不过在正式部署里我用得最多的还是第一种脚本指定方式因为它清晰、可控、注释好之后其他同事也很好维护。5. 部署jar包之前的最后一步确认jar包本身是健康的你可能会想jar包不就是mvn package一下就行了吗其实有很多坑在这一步埋着。我先给你看一个常规的打包流程再拆一下大家很关心的jar包的lib中是什么这个问题。5.1 用Maven打出可执行jar包假设你手头已经有一个Spring Boot项目执行打包最常用的命令是mvn clean package -DskipTests也可以跳过某些检查加快速度mvn clean package -DskipTests -Dmaven.javadoc.skiptrue -Dmaven.test.skiptrue打包完成后target目录下会出现一个xxx-0.0.1-SNAPSHOT.jar。这里有一个常见的混淆点Spring Boot项目打出来的jar包分为可执行jar包和原始jar包。如果你发现启动时报no main manifest attribute那就说明你打的是原始jar包不是可执行jar包因为它的MANIFEST.MF里缺少Main-Class和Start-Class信息。解决方式是在pom.xml的spring-boot-maven-plugin配置里确保没有配置成repackage的skipbuild plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build5.2 jar包里的lib目录到底是什么jar包里的lib目录是什么是很多人刚接触时都会问的问题。其实Spring Boot的可执行jar包内部结构大致是这样的myapp.jar ├── BOOT-INF │ ├── classes │ │ └── 你自己的类和配置文件 │ └── lib │ └── 所有依赖的第三方jar包 ├── META-INF │ ├── MANIFEST.MF │ └── maven └── org └── springframework └── boot └── loaderBOOT-INF/lib放的就是项目所有依赖的第三方库Spring Web、MySQL驱动、JSON解析这类全在里面。所以理论上一个Spring Boot可执行jar包是自带全家桶的你把它拷到任何装有JDK的Linux机器上都能跑不需要额外安装项目依赖。这也是为什么部署一个jar包比部署传统的war包要爽这么多。5.3 用Linux常用命令检查jar包和文件完整性在把jar包发到服务器之前有几个linux常用命令值得养成习惯# 查看jar包大小防止传到中途文件损坏 ls -lh myapp.jar # 查看jar包内的文件列表 jar tf myapp.jar | head -20 # 解压并检查MANIFEST unzip -p myapp.jar META-INF/MANIFEST.MF文件传输建议直接用scp或者用sftp工具。如果机器在内网隔离环境也可以先上传到跳板机再转这点我就不展开说了关键是传完之后立刻比对一下大小或者校验值md5sum myapp.jar确认无误再进行部署。6. 部署jar包的完整流程从启动到日志到自启动部署这一环节最核心的问题就四个怎么启动、怎么后台运行、日志去哪看、开机能不能自动跑起来。这里分享一下我平时用的标准套路尤其是systemd方式强烈推荐给所有认真做部署的人。6.1 最简单的启动方式与看起来卡住了的真相直接用java -jar myapp.jar是可以启动的但此时你的终端会卡在前台日志密密麻麻地滚动一按CtrlC进程就死了。很多新手以为启动了但又没完全启动实际上是没搞懂前台运行和守护进程的区别。在测试环境里前台运行其实挺方便的你可以直接看日志确认Spring Boot是否成功启动。但要是部署到生产环境后台运行几乎是必须的。6.2 nohup方式简单但粗糙nohup算是经典方案意思就是挂起不挂断配合放到后台nohup java -jar myapp.jar --server.port8080 app.log 21 这句命令的解释是不挂断地运行jar包把标准输出和错误输出都重定向到app.log文件里然后放后台。用的时候要记住和21的含义前者是输出重定向后者是把错误输出也塞进同一个日志文件。如果你漏掉21报错信息会直接飘到屏幕上日志文件里却干干净净排查问题的时候会非常懵。nohup方式的问题有不少。进程是用kill命令手动管理的不方便开机自启动你得自己写脚本塞进rc.local或者cron里进程意外退出没人管。所以它也就在临时调试、快速验证的场合用用。6.3 systemd方式生产环境最佳实践如果你想让jar包部署得像一个正经的服务一样能用systemctl start/stop/restart命令控制还能开机自启动那必须用systemd。在/etc/systemd/system目录下创建一个service文件内容大致如下[Unit] DescriptionMy Spring Boot Application Afternetwork.target [Service] Typesimple EnvironmentJAVA_HOME/usr/local/java/jdk17 EnvironmentPATH/usr/local/java/jdk17/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin WorkingDirectory/opt/myapp ExecStart/usr/local/java/jdk17/bin/java -Xms256m -Xmx512m -jar /opt/myapp/myapp.jar StandardOutputappend:/opt/myapp/app.log StandardErrorappend:/opt/myapp/app.log Restartalways RestartSec5 Userwwwdata [Install] WantedBymulti-user.target这条配置里几个关键点我给你解释一下EnvironmentJAVA_HOME...既保证了服务自己知道Java在哪又不会污染全局环境。ExecStart直接指定JDK的绝对路径这就实现了linux指定jdk运行jar文件的目标。Restartalways进程意外退出后5秒自动重启这是裸nohup完全做不到的可靠性。Userwwwdata不要用root跑应用进程单独建一个低权限用户这是我在生产环境踩过坑之后形成的习惯。写完配置后执行sudo systemctl daemon-reload sudo systemctl enable myapp.service sudo systemctl start myapp.service接下来的常用命令就变成sudo systemctl status myapp.service sudo systemctl restart myapp.service sudo journalctl -u myapp.service -fjournalctl -u myapp.service -f就是实时查看日志的姿势非常直观。使用systemd这种服务化部署方式后即使jar包崩溃退出系统也会自动把它拉起来整个过程不需要人盯着。7. jar包部署常见故障与排查思路在这个环节我把过去几年里在linux上部署jar包时遇到的高频问题整理成了一份速查表。你遇到问题的时候先不要慌对照这个表找找原因大概率能解决。故障现象大概率原因推荐排查命令/手段bash: java: command not foundPATH没配好或JDK没安装成功检查/usr/local/java目录、查看/etc/profileJava版本不对系统里有多个JDKPATH优先级不对which -a java、echo $PATH启动报no main manifest attribute误用了原始jar包而不是可执行jar包unzip -p jar包 META-INF/MANIFEST.MF端口占用8080被其他进程占了netstat -tlnp | grep 8080 或 ss -tlnp内存溢出-Xmx设置太大或太小或系统内存不足free -h、看日志里的OutOfMemoryError配置文件外置不生效配置文件没在jar包同级目录确认application.yml的加载位置jar包没执行权限手动解压或传输后权限丢失chmod x myapp.jar或直接java -jar运行启动后很快自动退出存在System.exit或者依赖的服务没就绪看日志最后几行、检查数据库/Redis连接中文乱码系统字体和文件编码问题java启动参数加-Dfile.encodingUTF-87.1 port already in use这类问题的处理思路一半以上的部署失败都和端口有关。Spring Boot默认8080第二个项目上去肯定报端口占用。netstat -tlnp | grep 8080看到PID之后可以用ps -fp PID看是什么进程。确认是旧项目占用就考虑指定新端口启动或者在配置里改server.port。改端口的时候还要注意代码里其他地方的硬编码地址会不会因此失效。7.2 日志排查法先看最后100行排查任何启动故障第一步都应该看日志。用tail -100 app.log看最后100行或者tail -f app.log实时追踪。Spring Boot启动日志有个特点只要出现了Started Application in x seconds就基本稳了而异常堆栈多半发生在Application run failed前后。我遇到过一种特别奇怪的情况jar包本地跑得好好的上传到Linux服务器之后启动就报类找不到。后来发现是打包时不小心混入了Windows下才能加载的东西或者依赖里用了带版本差异的类。这种时候别慌着改代码先看堆栈信息里的ClassNotFoundException指向哪个类再检查依赖版本。7.3 JDK下载装好后环境变量不生效的排查技巧jdk环境变量配置失败这个热搜词下充满了各种问题贴。最常见的场景是明明把export JAVA_HOME/usr/local/jdk17写进了/etc/profile但echo $JAVA_HOME还是空。这是为什么第一你当前shell可能没执行source /etc/profile新写入的配置不会自动加载。第二你写入的文件和当前shell不对应。比如你用的是zsh而配置写进了.bashrc。解决办法很简单把配置写进~/.bashrc再source对应文件或者直接新开一个会话。第三/etc/profile里可能有一行逻辑会忽略非交互shell的加载。遇到这种比较玄学的情况建议直接看~/.bashrc避免在profile逻辑里绕圈。写在最后的一点经验整个流程走下来你会发现装JDK本身不复杂无非是下载、解压、配环境变量真正的复杂度在于让多个项目各取所需让服务可靠地跑起来。我个人最推荐的做法是生产环境用systemd管理jar包服务在service文件里直接用/usr/local/java/jdk17/bin/java完整路径启动这样既能绕过各种全局变量的坑又能从根源上避开系统里多个JDK谁知道用的是哪个的尴尬。试过一两次之后你会觉得要把一个jar包部署得既稳又干净真没想象中那么难。
返回列表