1. 项目概述
1.1 核心需求解析
"服务器Java开发环境配置"这个标题看起来简单,但实际做起来远比装个JDK复杂得多。我这些年帮团队搭建过不少服务器环境,也接手过别人留下的烂摊子,深知这里面水很深。很多人以为在服务器上配好Java环境就是把JDK解压、配个环境变量就完事了,等真正部署项目时才发现各种问题:版本冲突、内存分配不合理、日志撑爆磁盘、时区导致时间差八小时……每一样都让人头大。
这篇文章面向的人群很明确:刚接触服务器部署的Java开发新人、需要从零初始化服务器环境的全栈工程师、以及想规范团队开发环境的TL。核心目标只有一个——让你看完之后,能一步步在Linux服务器上搭出一个干净、稳定、可维护的Java运行环境。
先说清楚这篇文章解决什么问题:不光是"装好能跑"这么简单,而是要让你理解每一步操作背后的逻辑,比如为什么选这个JDK版本、为什么JVM参数要那样配、为什么环境变量要写进profile.d而不是直接改/etc/profile。只有把这些"为什么"搞清楚了,你才能举一反三,遇到别的环境问题也能自己排查。
我见过太多反面教材:有人在生产服务器上直接apt install default-jdk,装了个不知道什么版本的OpenJDK,结果项目跑起来各种诡异报错;有人把JDK装到一半发现磁盘空间不够,只能尴尬地清缓存;还有人环境变量配在~/.bashrc里,加了个定时任务用cron执行Java程序,结果环境变量压根没加载,程序直接起不来。这些坑,这篇文章都会帮你避开。
下面我按一套完整的实操流程来写,从方案选型到每一步配置命令,再到常见问题排查,全部基于我实际踩过的坑和经验总结,你照着做基本不会翻车。
1.2 适用场景与整体方案选型
服务器上配Java开发环境,最常见的有这么几种场景:单人开发测试的云服务器、团队共用的开发机、以及即将上线项目的生产环境。不同场景对稳定性和性能的要求完全不一样,所以我建议你先想清楚自己属于哪种情况,再决定怎么做。
很多人纠结一个问题:到底用Oracle JDK还是OpenJDK?我的建议是:新项目一律用OpenJDK,版本选LTS(长期支持版),目前主流是Java 8或Java 17。理由很简单:免费、社区支持好、和Spring Boot等主流框架兼容性没问题。至于Oracle JDK,除非你们公司有商业授权需求或者遗留项目强制要求,否则没必要给自己找麻烦。
服务器系统方面,我强烈推荐CentOS 7/8或者Ubuntu 20.04/22.04 LTS。CentOS 7虽然已经停止维护了,但国内很多云服务器厂商的镜像还是以它为主,存量市场很大;Ubuntu LTS则胜在社区活跃、软件源新。你选哪个都行,关键是操作习惯要统一,别这台机器用yum那台用apt,维护起来会疯掉的。
再说远程连接方式,建议直接用密钥登录而不是密码登录,安全性和便利性都好得多。配置好SSH之后,后续所有操作都通过SSH终端完成,Windows用户可以用Xshell或者直接用Windows Terminal里的SSH命令,Mac和Linux用户就更是原生支持了。
整体配置流程我梳理成了下面这张表,后面每个环节都会详细展开:
| 步骤 | 操作内容 | 核心目的 | 预计耗时 |
|---|---|---|---|
| 1 | 服务器基础环境检查与优化 | 确认系统、资源、网络正常 | 10分钟 |
| 2 | JDK安装与环境变量配置 | 提供Java运行基础 | 15分钟 |
| 3 | Maven/Gradle构建工具配置 | 项目构建与依赖管理 | 10分钟 |
| 4 | Git与代码同步配置 | 代码版本管理与拉取 | 10分钟 |
| 5 | 环境验证与多版本切换 | 确认一切就绪 | 10分钟 |
这个流程不是我拍脑袋定的,而是多年实操总结出的最优顺序。先搞定基础系统层,再装JDK,然后是构建工具和版本管理工具,最后做整体验证。每一步都建立在上一步的基础上,层层递进,出了问题也容易定位是哪一环节的锅。
2. 配置前的准备工作与基础环境检查
2.1 系统版本与硬件资源摸底
拿到一台新服务器,先别急着装东西,花几分钟把底摸清楚。这一步能帮你避免后面很多麻烦,尤其是那些装到一半才发现问题的尴尬局面。
先看系统版本:
cat /etc/os-release这个命令在CentOS、Ubuntu上都适用,会输出系统的名称和版本号。知道系统版本很关键,因为不同版本的包管理器、软件源地址都不一样,网上搜到的教程经常因为系统版本对不上而失效。
接下来看硬件资源:
free -h # 查看内存 df -h # 查看磁盘空间 nproc # 查看CPU核数这三个命令必须跑一遍。内存大小直接决定你JVM堆内存怎么配,磁盘剩余空间决定你能不能装得下JDK、Maven仓库和各种中间件,CPU核数则影响G1垃圾回收器的配置策略。我见过有人2G内存的服务器非要给JVM分配1.5G堆内存,结果系统本身加数据库直接OOM,整个服务都崩了。
这里有个很重要的判断逻辑:只要你是在云服务器上装Java环境,我默认你用的是Linux系统,Windows Server不在本文讨论范围内。虽然Windows Server也能跑Java,但生产环境99%都是Linux,这篇文章所有命令和配置也都是基于Linux的,你别在Windows上硬套。
还有网络连通性,新手最容易忽略。服务器如果要访问外网下载依赖包,你需要确认网络正常:
ping -c 4 baidu.com curl -I https://repo.maven.apache.org/maven2/如果公司服务器在内网,可能还需要配置代理或者内网镜像源。这一步提前确认好,能避免后面Maven下载依赖时一堆超时错误。
2.2 系统时区与基础软件包安装
时区这个问题,我放到前面说,因为太重要了。默认情况下很多云服务器的系统时区是UTC,比北京时间慢8个小时。日志里的时间戳全是错的,定时任务也乱套,排查问题的时候时间对不上,简直让人抓狂。
timedatectl set-timezone Asia/Shanghai timedatectl status # 确认时区已切换执行完这两条命令,系统时间就和北京时间同步了。这里顺便说一下,如果你后面装数据库、Redis这类中间件,它们也会读取系统时区,所以这一步做对了能省后面一堆事。
接着装一些基础软件包,都是后面用得上的工具:
CentOS/RHEL系列:
yum install -y vim net-tools wget curl lrzsz unzip zipUbuntu/Debian系列:
apt update && apt install -y vim net-tools wget curl unzip zip这些工具各自的作用:
- vim:编辑配置文件必备,虽然你也可以用nano,但vim在Linux运维中是通用技能
- net-tools:提供ifconfig、netstat等命令,排查网络问题好用
- wget/curl:下载文件、测试接口连通性
- lrzsz:提供rz/sz命令,方便从本地直接传文件到服务器
- unzip/zip:解压和压缩,后面处理JDK打包文件、项目发布包都要用
这里面有个小经验:lrzsz在远程终端里用特别方便,但如果你用的终端工具本身支持拖拽上传(比如某些IDE的Remote Development插件),那这个包就可装可不装。不过为了保险起见,我还是建议装上,毕竟轻量又实用。
2.3 SSH安全加固与登录配置
远程登录服务器最怕什么?密码被暴力破解。默认的22端口每天会有无数扫描机器人在尝试爆破,所以我建议从第一天起就做好SSH安全配置。
第一步,生成密钥对。如果你还没有密钥,在本地电脑执行:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"生成后,把公钥传到服务器:
ssh-copy-id root@服务器IP或者手动把~/.ssh/id_rsa.pub的内容追加到服务器的~/.ssh/authorized_keys文件里。配好之后,从本地登录服务器就不需要密码了。
第二步,修改SSH配置,禁用密码登录:
vim /etc/ssh/sshd_config找到下面几项,修改成:
PermitRootLogin prohibit-password PasswordAuthentication no PubkeyAuthentication yes然后重启SSH服务:
systemctl restart sshd注意:改这个配置之前一定确认密钥登录已经生效,否则一旦断开连接你就进不去了。我有个同事就干过这事,改完配置重启SSH,发现自己密钥没传上去,服务器直接失联,最后只能通过云厂商的控制台VNC去救援,折腾了一个多小时。
做完SSH加固,还有几个安全层面的细节可以顺手做了:防火墙只放行必要的端口(22、80、443,以及你Java应用要用的业务端口),云安全组也同步限制来源IP。这些不做硬性要求,但生产环境必须注意。
3. JDK安装与环境变量配置详解
3.1 OpenJDK版本选择与下载技巧
这节是全文的核心,JDK装得好不好,直接决定你后面所有Java程序能不能稳定运行。
版本怎么选?我前面说了优先LTS版本,但具体选Java 8还是Java 17,要看你的项目技术栈:
- 如果项目用Spring Boot 2.x,那Java 8是标配,稳得很
- 如果项目用Spring Boot 3.x,那必须Java 17及以上,因为Spring Boot 3直接抛弃了Java 8
- 如果纯粹是学习或者新起的小项目,直接上Java 17,新特性用起来真香
确认好版本之后,下载方式有两种。一种是直接用包管理器装,CentOS用yum、Ubuntu用apt:
# CentOS安装Java 8 yum install -y java-1.8.0-openjdk-devel # Ubuntu安装Java 17 apt install -y openjdk-17-jdk这种方式最省事,但有一个问题:你没法完全控制安装路径和版本细节,而且某些精简版系统的源里包名可能不一样。对于需要精确控制环境的场景,我建议用第二种方式。
第二种是从官网下载tar.gz手动安装。以JDK 17为例:
# 先建目录 mkdir -p /usr/local/java cd /usr/local/java # 下载(注意替换为实际可用的下载链接) wget https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz # 解压 tar -zxvf openjdk-17.0.2_linux-x64_bin.tar.gz手动安装的好处是路径可控、多个版本可以共存,后续切换JDK版本也方便。坏处就是每次升级要手动下载解压,稍微麻烦一点。我个人习惯:开发测试机用包管理器装,生产环境手动安装指定版本,用哪个心里有数。
下载的时候有个细节:OpenJDK官方下载页面的链接经常带一长串哈希路径,直接用wget复制链接时容易出错。所以我的建议是先在本地浏览器把链接复制好,确认url完整再粘贴到终端,避免少了一段导致404。
3.2 环境变量配置的正确姿势
JDK解压完之后,接下来就是配置环境变量。这一步看起来简单,但很多人在这踩坑,踩得最多的就是不知道把配置写在哪个文件里。
网上很多教程让你直接改/etc/profile,然后全部环境变量都往里面塞。这个做法我极其不推荐,因为/etc/profile是系统级全局配置,所有用户、所有shell都会加载,一旦写错了影响面非常大。而且如果你后面装Maven、Tomcat、Node等一堆东西,全塞进/etc/profile,那个文件会乱成一锅粥。
更优的做法是利用/etc/profile.d目录。系统在加载/etc/profile时会自动加载这个目录下所有的.sh文件,所以你只需要新建一个专门的文件来放Java的环境变量,彼此隔离、互不影响:
vim /etc/profile.d/java.sh写入以下内容:
# JDK 17 环境变量配置 export JAVA_HOME=/usr/local/java/jdk-17.0.2 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib保存后让它立即生效:
source /etc/profile.d/java.sh为什么CLASSPATH要设置成那样?因为Java在编译和运行时会用CLASSPATH来查找类文件,点号代表当前目录。虽然JDK 9之后类库机制改了,CLASSPATH不再是必须的,但保留下来兼容老项目没坏处。
然后验证安装是否成功:
java -version javac -version如果输出类似这样,就说明JDK装好了:
java version "17.0.2" 2022-01-18 LTS Java(TM) SE Runtime Environment (build 17.0.2+8-86) Java HotSpot(TM) 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)3.3 多JDK版本共存与快速切换
有时候一台服务器上要跑多个项目,老项目要Java 8,新项目要Java 17,这就涉及多版本共存的问题。解决方法很简单:把不同版本的JDK放在不同目录,然后用alternatives命令或者手动切换PATH。
以alternatives方式为例:
# 注册不同JDK版本 alternatives --install /usr/bin/java java /usr/local/java/jdk-8u402/bin/java 1 alternatives --install /usr/bin/java java /usr/local/java/jdk-17.0.2/bin/java 2 # 切换版本 alternatives --config java执行后会显示一个列表,输入对应的数字就切过去了。
如果你用的是自定义的profile.d配置,切换版本就得改JAVA_HOME指向。我有个项目实践觉得挺好用:在profile.d里建一个软链方案,让JAVA_HOME指向一个软链接,以后只需要改软链接指向就能切换:
# 在 /usr/local/java 下创建软链接 ln -s jdk-17.0.2 current # JAVA_HOME 指向 export JAVA_HOME=/usr/local/java/current以后要切到Java 8,只需删除软链接重新指一下:
rm -f /usr/local/java/current ln -s jdk-8u402 /usr/local/java/current这个方案的优点是其他配置都不用动,Maven、IDEA这些工具读到JAVA_HOME就自动跟着切过去了,非常方便。
4. Maven与项目构建工具配置
4.1 Maven安装和仓库镜像加速
Java项目肯定离不开构建工具,目前Maven还是绝对的主流,Gradle在Android和部分新项目里也用得很多。建议两个都了解一下,但日常用Maven就足够了。
Maven的安装方式和JDK很像,可以用包管理器装,也可以手动装。这里我讲手动装,因为版本控制更灵活:
# 下载Maven(以3.9.6为例) cd /usr/local wget https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz tar -zxvf apache-maven-3.9.6-bin.tar.gz mv apache-maven-3.9.6 maven然后配置环境变量,还是在profile.d下新建文件:
vim /etc/profile.d/maven.shexport MAVEN_HOME=/usr/local/maven export PATH=$MAVEN_HOME/bin:$PATHsource /etc/profile.d/maven.sh mvn -v看到Maven版本信息就算成功了。这里有个细节:Maven运行依赖JAVA_HOME环境变量,所以必须先装好JDK并且JAVA_HOME配置正确,否则mvn命令会报错找不到Java环境。
装好Maven之后最重要的一步是配置国内镜像源。Maven默认的中央仓库在国外,下载依赖速度慢到怀疑人生,尤其在公司网络环境下经常超时。修改配置文件:
vim /usr/local/maven/conf/settings.xml找到 节点,在里面加入:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>镜像源不是随便配个就行,你需要注意mirrorOf的配置。central表示只镜像中央仓库,如果你项目中还用到其他仓库(比如JitPack),可以改成*号全部镜像,但一般建议保持central,避免影响特殊仓库的获取逻辑。
这是一个典型的"知其所以然"的点:镜像的原理其实是在你本地和中央仓库之间加了一个代理,你下载依赖的请求先发到阿里云,阿里云那边缓存了海量依赖,直接返回给你,速度自然快很多。
4.2 Maven本地仓库管理与多项目配置
Maven的本地仓库默认在~/.m2/repository,第一次构建项目时会下载大量依赖到这个目录。这里有几个管理建议,都是经验之谈:
第一,确认本地仓库路径。如果你是用root用户操作的,默认就是/root/.m2/repository;如果用普通用户部署,就是/home/用户名/.m2/repository。要注意严格区分用户,因为Maven会把依赖缓存在当前用户目录下,不同用户之间不共享。
第二,如果有条件,给Maven仓库目录所在的磁盘多留点空间。一个中型项目所有依赖加起来轻轻松松超过1G,大型项目3-5G都很正常。用df -h确认一下你的/目录或者home目录空间够不够,别到时候构建到一半磁盘写满。
第三,如果你的服务器要部署多个Java项目且共用一套Maven环境,建议在settings.xml里统一配置本地仓库路径,方便管理和清理:
<settings> <localRepository>/data/maven-repository</localRepository> </settings>这样所有项目、所有用户共享同一个依赖缓存,构建速度更快,磁盘占用也更集中。
还有一个小技巧:Maven的settings.xml可以配置profile来区分不同环境(开发、测试、生产),每个profile可以指定不同的仓库地址和参数。虽然这部分不是环境配置的必选项,但提前了解对后面项目上线很有帮助。
4.3 Gradle配置要点(备选方案)
如果你的项目用的是Gradle,那配置思路也差不多。Gradle安装同样有包管理器方式和手动方式,但要注意Gradle和JDK版本有对应关系:Gradle 7.x支持JDK 8到17,Gradle 8.x要求JDK 8到21。装之前先查一下版本的兼容性表。
Gradle的仓库加速配置在项目的build.gradle或者init.gradle文件中,通常用阿里云镜像:
allprojects { repositories { maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://maven.aliyun.com/repository/central' } mavenCentral() } }Gradle默认的依赖缓存目录是~/.gradle,和Maven互不干扰。如果你同时用Maven和Gradle管理不同项目,要记住它们是两套独立的缓存体系,别搞混了。
5. Git、项目同步与代码拉取配置
5.1 Git安装与全局配置
装完JDK和构建工具,接着搞定代码版本管理工具Git。这一步对服务器部署至关重要,因为大多数项目都是通过Git拉取代码到服务器上部署的。
安装很简单:
# CentOS yum install -y git # Ubuntu apt install -y git装完之后做全局配置:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"这个全局配置在服务器上主要是为了规范提交信息,虽然你大概率不会在服务器上手动提交代码,但某些自动化脚本可能会用到。
然后是SSH密钥配置。服务器拉取私有仓库代码时,推荐用SSH方式而不是HTTPS方式,因为HTTPS每次都要输密码或者配token,SSH则一劳永逸:
ssh-keygen -t rsa -b 4096 -C "deploy@server" cat ~/.ssh/id_rsa.pub把输出的公钥内容添加到Git仓库(GitHub、Gitee、GitLab都行)的Deploy Key里。之后在服务器上执行git clone测试一下:
git clone git@github.com:yourname/yourproject.git /data/projects/yourproject如果克隆成功,就说明Git环境全部就绪了。
这里有个实战建议:在服务器上部署项目时,不要把代码直接放在root用户的home目录下,建议单独建一个目录比如/data/projects,然后用专门的应用账号(比如app用户)来管理代码和启动服务。这样即使应用被入侵,攻击者也拿不到root权限,安全等级完全不一样。
5.2 代码同步策略与项目目录规划
代码同步上,我吃过不少亏,总结出来一套还算靠谱的目录规范和流程。
目录规划建议:
/data/ ├── projects/ # 所有项目代码放这里 │ ├── demo-api/ # 后端服务 │ ├── demo-admin/ # 管理后台 │ └── demo-web/ # 前端项目 ├── backups/ # 备份目录 ├── logs/ # 统一日志目录 └── app/ # 应用部署目录为什么代码目录和部署目录分开?因为编译后的产物(jar包、war包、静态文件)和源码不应该混在一起。源码通过Git拉取更新,部署目录则从构建结果复制或软链,这样每次发布都干干净净。
同步代码有两种常见策略:
第一种,直接在生产服务器上git pull更新代码,然后重新构建、重启应用。这种方式简单直观,适合中小团队,但需要服务器上有完整的构建工具链。
第二种,在CI/CD平台(Jenkins、GitLab CI等)上构建好产物,通过scp/rsync等方式同步到服务器。这种方式生产环境更干净,服务器只需要运行时的Java环境即可,不需要装Maven/Gradle。如果你的服务器是纯生产环境,我强烈建议用这种方式,能大幅度降低环境出问题的概率。
rsync同步命令示例:
rsync -avz --progress jenkins@构建机IP:/data/build/demo-api.jar /data/app/demo-api/5.3 环境变量与配置文件的脱敏管理
这个点很多人会忽视,但非常关键:项目里的数据库密码、Redis密码、第三方API密钥等敏感信息,千万不要硬编码在代码里然后通过Git库分发。我在服务器上见过太多因为配置文件泄露导致的安全事故。
推荐的方案有两种:
一种是用环境变量的方式注入配置。Spring Boot项目可以直接在application.yml里用${DB_PASSWORD}这种占位符,然后在启动脚本或者systemd服务配置里设置环境变量:
spring: datasource: password: ${DB_PASSWORD}启动时:
export DB_PASSWORD="your_strong_password" java -jar demo-api.jar另一种是用配置文件外部化。把application-prod.yml放在jar包外面,通过--spring.config.location参数指定位置,这样配置文件不进Git库,每次部署只需要把配置文件放到约定位置即可。
我个人的习惯是两者结合:敏感信息用环境变量,非敏感配置用外部化配置文件。既照顾了安全性,又方便不同环境切换配置。
6. 环境验证与常见问题排查
6.1 全链路验证:从Java到项目启动
所有环境配好之后,别急着部署项目,先做一轮完整的验证流程。这一步能把90%的配置问题提前暴露出来,省得你在项目启动时报一堆莫名其妙的错再来反查环境。
验证清单如下:
# 1. 检查JDK版本 java -version javac -version # 2. 检查JAVA_HOME路径 echo $JAVA_HOME # 3. 检查Maven版本 mvn -v # 4. 检查Git版本 git --version # 5. 确认时区正确 date # 6. 确认端口可用(以一个业务端口8080为例) ss -lntp | grep 8080每个检查项背后都有目的:java和javac的版本必须一致,否则编译和运行用的是两套环境,可能出现编译时用的JDK 17特性但运行时只有JDK 8导致的NoSuchMethodError;JAVA_HOME路径确认环境变量指向正确;Maven版本确认构建工具可用;时区确认避免日志时间错乱;端口确认避免应用启动时端口被占用。
做完这些基础验证,再拉一个最简单的Spring Boot项目试跑一下:
# 新建一个临时项目目录 cd /tmp mvn archetype:generate -DgroupId=com.demo -DartifactId=hello -DarchetypeArtifactId=maven-archetype-quickstart # 构建 cd hello mvn clean package # 用Java直接跑一个最简单的类验证 java -cp target/hello-1.0-SNAPSHOT.jar com.demo.App能正常输出输出hello world,就说明整条链路JDK -> Maven -> 编译 -> 运行全部通了。这一步做完,你再部署正式项目就会顺畅很多。
6.2 高频问题排查实录:JAVA_HOME与版本不一致
这里我整理几个我实际运维中遇到的高频问题,每个都是血泪教训换来的经验。
问题一:java -version有输出但mvn -v报错找不到Java环境。
这个问题的根因通常是JAVA_HOME配置不对。Maven本身不用Java环境启动,但它启动后要解析JAVA_HOME去找JDK。排查步骤:
echo $JAVA_HOME ls -l $JAVA_HOME/bin/java如果JAVA_HOME是空的或者路径不对,重新按照前面说的profile.d方案配置一遍。注意配置完一定要source或者重新登录终端,当前终端会话不会自动加载新的环境变量。
问题二:编译时报错"java: command not found"。
这个更基础,通常是PATH环境变量没包含JDK的bin目录。检查:
echo $PATH | grep java如果输出为空,说明PATH配置有问题,重新检查profile.d/java.sh里的export PATH行。
问题三:项目能编译但启动时报UnsupportedClassVersionError。
这个报错的意思是:编译用的JDK版本比运行用的JDK版本新。比如你用JDK 17编译的class文件,拿到JDK 8上运行,就会报这个错。解决办法是统一两端版本,要么降编译版本,要么升运行版本。我建议统一用JDK 17,老项目保持JDK 8,但同一套环境内不要混用。
问题四:Maven下载依赖特别慢或者一直卡住。
优先检查settings.xml里的镜像源是否生效。用mvn -X或mvn help:effective-settings查看实际生效的镜像配置。我遇到过一种情况:settings.xml写对了,但项目pom.xml里自定义了repository而且优先级更高,导致镜像没生效,依赖还是从国外仓库下载。
6.3 性能调优与JVM参数初始化
环境配置的最后一块拼图是JVM参数。很多项目跑着跑着就内存溢出、频繁Full GC,很大程度是因为JVM参数没配好。虽然这不是"环境配置"的必修课,但对服务器Java环境来说极其重要。
一个相对通用的启动参数模板(2核4G服务器为例):
java -server -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m \ -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/data/logs/heapdump.hprof \ -jar demo-api.jar参数解释:
- -Xms和-Xmx:堆内存初始值和最大值。设成一样可以避免JVM动态伸缩带来的性能损耗,但小内存机器建议留出余量
- -XX:MetaspaceSize:元空间初始大小,很多框架加载大量类时需要这个配置
- -XX:+UseG1GC:使用G1垃圾回收器,JDK 9之后的默认选择,对大堆和低延迟场景友好
- -XX:+HeapDumpOnOutOfMemoryError:OOM时自动导出堆转储文件,排查内存问题必备
- -XX:HeapDumpPath:堆转储文件的保存路径
内存分配的比例参考:服务器总内存4G时,JVM堆最大1G左右,留出内存给操作系统、Java进程自身、可能的数据库或缓存中间件。别贪心把所有内存都分给JVM,不然系统本身的内存都不够了。
日志和GC日志也要提前规划好:
-Xloggc:/data/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStampsGC日志能帮你分析性能瓶颈,排查线上问题少了它寸步难行。我见过太多服务的日志目录乱放,磁盘满了也不知道,所以强烈建议统一规划日志路径并定期清理。
6.4 快速排查命令速查表
最后送大家一个排查命令速查表,都是我日常用得最多的,建议收藏一下:
| 排查场景 | 命令 | 说明 |
|---|---|---|
| 查看Java进程 | ps -ef | grep java | 确认应用是否在运行 |
| 查看端口占用 | ss -lntp | grep 8080 | 确认端口是否被占 |
| 查看内存使用 | free -h | 确认系统内存余量 |
| 查看磁盘空间 | df -hT | 确认磁盘是否写满 |
| 查看应用日志 | tail -f /data/logs/app.log | 实时跟踪日志输出 |
| 查看GC日志 | grep "Full GC" /data/logs/gc.log | 快速定位Full GC频率 |
| 查看JVM堆内存 | jmap -heap 进程PID | 查看堆内存使用情况 |
| 线程转储 | jstack 进程PID | 排查死锁和线程卡顿 |
这些命令有什么作用,一般什么时候用,我简单标注一下:进程查看和端口检查是部署后第一件事,确认服务真的起来了;内存和磁盘是日常巡检必看项,很多问题在资源耗尽前会有预警;日志跟踪是定位问题的第一入口;jmap和jstack则是更深层次的工具,OOM和线程卡死时基本必备。
7. 项目部署与日常维护建议
7.1 系统服务方式管理Java应用
项目部署方式有很多种,最简单的就是nohup后台运行,但这种方式对运维很不友好:进程管理靠ps和kill,开机自启要手动处理,日志没轮转。我强烈建议用systemd来管理Java应用,这是现代Linux系统推荐的进程管理方式。
新建一个systemd服务文件:
vim /etc/systemd/system/demo-api.service内容模板:
[Unit] Description=Demo API Service After=network.target [Service] Type=simple User=app Group=app WorkingDirectory=/data/app/demo-api Environment="JAVA_HOME=/usr/local/java/current" Environment="SPRING_PROFILES_ACTIVE=prod" ExecStart=$JAVA_HOME/bin/java -server -Xms512m -Xmx1024m -jar /data/app/demo-api/demo-api.jar Restart=on-failure RestartSec=10s [Install] WantedBy=multi-user.target然后启用并启动:
systemctl daemon-reload systemctl enable demo-api systemctl start demo-api systemctl status demo-api这种方式的好处太多了:开机自启、自动重启、统一日志管理、用journalctl查看日志、systemctl控制启停。你从"手动部署"升级到"服务化部署",体验是质的飞跃。
有个细节要提醒:上面我用了User=app和Group=app,意味着服务以专门的app用户运行。这是生产环境的安全基线要求,应用进程不要用root跑,否则一旦应用有漏洞,整个服务器都沦陷了。创建app用户的命令:
useradd -m -s /bin/bash app7.2 日志轮转与磁盘空间管理
Java应用的日志如果不做管理,一个月就能把磁盘写满。我之前遇到过一台服务器日志文件涨到30G,直接把盘占满,数据库都连不上了。从那以后,我要求所有Java应用必须配日志轮转。
你可以用logrotate来做:
vim /etc/logrotate.d/demo-api/data/logs/demo-api/*.log { daily rotate 7 compress delaycompress missingok notifempty copytruncate }这个配置的含义:每天轮转一次,保留7份历史日志,压缩旧日志,如果文件缺失或为空不操作。copytruncate这个参数特别重要,它通过复制+截断的方式轮转日志,应用不需要重启就能继续写新的日志。
配置好之后,用logrotate -d /etc/logrotate.d/demo-api试跑一下,确认没有配置错误。
另外,应用自身的日志框架也要配置好滚动策略。Logback的配置里加上TimeBasedRollingPolicy,按天切分日志,保留30天,这样即使logrotate漏了,应用自己也有兜底。
7.3 安全基线检查与环境审计
环境配置完之后,别急着撒手不管,我建议做一次安全基线检查。这里列一个简单的检查清单:
- 是否禁用了root远程密码登录(前面SSH配置那步已做)
- 服务器防火墙是否只放行必要端口
- Java应用是否以非root用户运行
- 敏感配置是否通过环境变量注入而不是写死在代码里
- 日志目录的权限是否为应用用户可写、其他用户不可读
- 系统是否定期更新补丁
这些检查项看着多,其实每一项都对应着一类安全事故。比如日志权限设置不当,可能导致普通用户也能看到数据库连接信息;系统补丁不更新,可能被已知漏洞攻击。运维工作就是这样,可能90%的精力都花在预防那10%的小概率事件上。
我自己一般每隔一周会登录服务器跑一遍上面这些检查命令,顺手把日志清理一下,看看磁盘和内存使用。这个习惯坚持了很多年,帮我躲过了好几次灾难性的服务器故障。
8. 我踩过的那些坑:环境配置经验谈
写到这里,整套服务器Java开发环境配置的流程就完整了。最后分享几个我真实踩过的坑,都极具代表性,希望你不用再走我走过的弯路。
第一个坑是JDK版本混淆。有一次我在测试服务器上装了两个JDK版本,用alternatives切换了默认版本,但Maven的surefire插件在编译时却还是用旧的JDK路径。排查了半天才发现是IDEA远程开发时,IDEA自身读的是~/.mavenrc文件里的配置,而不是系统的JAVA_HOME。这类工具链配置覆盖系统配置的情况很常见,遇到版本不一致的问题,优先排查有没有其他配置文件在"捣乱"。
第二个坑是滥用/etc/profile。早年间我习惯把所有环境变量都写在/etc/profile里,后来装了JDK、Maven、Redis、Tomcat、Node等一堆东西,那个文件膨胀到了上百行。某次升级Redis时不小心删错了一行,结果整个系统的环境变量全乱了,所有服务起不来。那是我第一次深刻体会profile.d目录存在的意义:用一个文件管一个软件的环境变量,删错了也只是某一个软件挂掉,不会全军覆没。
第三个坑是生产服务器装JDK时图省事直接apt install。当时看着省了5分钟,后来项目用了一些JDK内部类和方法,生产环境没有这些类,导致线上直接崩溃。从那天起,我给自己定了条规矩:生产环境必须手动安装并锁定JDK版本,绝对不依赖包管理器自动拉起。
还有一个小技巧我觉得特别实用:每台服务器配置完后,把安装版本、路径、部署信息记录在一个MD文件里,放到服务器的/data/docs目录下。半年后你再看,就会明白这个记录有多值钱。否则你完全想不起来这台服务器装的是什么版本的JDK、为什么某些路径要这样规划。
根据我的经验,只要把前面几步踏踏实实走完,你的服务器Java环境就算配置到位了。接下来要做的就是在真实项目中不断积累经验了。环境的坑是有限的,踩一个少一个,但项目的坑是无限的。祝你少踩坑,多用这些时间把精力花在业务代码上。