确保系统源可用(已切换国内源并配置 IPv4),更新系统:
sudo apt update && sudo apt upgrade -y
# 安装 OpenJDK 8
sudo apt install openjdk-8-jdk -y
# 安装 OpenJDK 17(推荐开源版本)
sudo apt install -y openjdk-17-jdk
# 验证安装
java -version
# 列出系统中所有已安装的 JDK 路径
ls /usr/lib/jvm/
# 查看 java 命令的备选配置(安装 JDK 17 后不会自动添加到这里)
sudo update-alternatives --list java
# 配置 java 命令的默认版本
sudo update-alternatives --config java
有 2 个候选项可用于替换 java (提供 /usr/bin/java)。
选择 路径 优先级 状态
------------------------------------------------------------
* 0 /usr/lib/jvm/java-17-openjdk-amd64/bin/java 1711 自动模式
1 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1111 手动模式
2 /usr/lib/jvm/java-8-openjdk-amd64/bin/java 1081 手动模式
要维持当前值[*]请按<回车>,或者键入选择的编号:
# 删除编号为1的备选项(根据你的实际编号调整)
sudo update-alternatives --remove java /usr/lib/jvm/java-17-openjdk-amd64/bin/java
# 注册JDK 8(优先级1081)
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java 1081
# 注册JDK 17(优先级1711)
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-17-openjdk-amd64/bin/java 1711
# 查看所有已安装的 openjdk 包
dpkg --list | grep -i openjdk
# 卸载 JDK 17(包名需与实际匹配)
sudo apt remove --purge -y openjdk-17-jdk
# 清理残留依赖
sudo apt autoremove -y
# 查看当前 java 备选配置
sudo update-alternatives --list java
# 删除指定路径的备选(替换为实际残留路径)
sudo update-alternatives --remove java /usr/lib/jvm/java-17-openjdk-amd64/bin/java
# 创建用户组
sudo groupadd -r zoc-app
# 创建无登录权限的运行用户
sudo useradd -r -s /sbin/nologin -g zoc-app zoc-app
# 创建程序主目录
sudo mkdir -p /opt/java-app/zoc/{bin,logs,conf}
目录说明:
/opt/java-app/zoc:程序根目录
bin/:启停脚本目录
logs/:日志目录
conf/:配置文件目录
# 递归设置目录属主为 zoc-app
sudo chown -R zoc-app:zoc-app /opt/java-app/zoc
# 设置目录基础权限(运行用户可读写,同组只读)
sudo chmod -R 750 /opt/java-app/zoc
将 Java 程序包(zoc.jar)及配置文件(application-prod.yml)上传至对应目录:
# 上传 jar 包到程序根目录
sudo cp /path/to/local/zoc.jar /opt/java-app/zoc/
# 上传配置文件到 conf 目录
sudo cp /path/to/local/application-prod.yml /opt/java-app/zoc/conf/
# 再次设置文件属主
sudo chown zoc-app:zoc-app /opt/java-app/zoc/zoc.jar
sudo chown zoc-app:zoc-app /opt/java-app/zoc/conf/application-prod.yml
cat > /opt/java-app/zoc/bin/start.sh << 'EOF'
#!/bin/bash
APP_NAME="zoc"
APP_BASE_DIR="/opt/java-app/zoc"
JAR_PATH="${APP_BASE_DIR}/zoc.jar"
CONF_PATH="${APP_BASE_DIR}/conf/application-prod.yml"
JVM_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"
if [ $# -ne 1 ]; then
echo "用法:$0 <端口号>(如:./start.sh 8080)"
exit 1
fi
PORT=$1
# 检查端口占用
if netstat -tulpn | grep -q ":${PORT} "; then
echo "端口 ${PORT} 已被占用"
exit 1
fi
LOG_FILE="${APP_BASE_DIR}/logs/${APP_NAME}-${PORT}.out"
PID_FILE="${APP_BASE_DIR}/logs/${APP_NAME}-${PORT}.pid"
# 启动实例
echo "启动实例(端口${PORT})..."
sudo -u zoc-app nohup java ${JVM_OPTS} \
-jar ${JAR_PATH} \
--spring.config.location=${CONF_PATH} \
--server.port=${PORT} \
> ${LOG_FILE} 2>&1 &
echo $! > ${PID_FILE}
echo "启动成功!日志:${LOG_FILE}"
EOF
指定jdk版本:
#!/bin/bash
# 核心配置:指定要使用的JDK版本路径
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
# 应用基础配置
APP_NAME="trafficfruit"
APP_BASE_DIR="/opt/java-app/tf"
JAR_PATH="${APP_BASE_DIR}/trafficfruit.jar"
CONF_PATH="${APP_BASE_DIR}/conf/application-prod.yml"
JVM_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -Dfile.encoding=UTF-8"
# ###########################
# 前置检查:确保JDK路径有效
# ###########################
if [ ! -d "${JAVA_HOME}" ]; then
echo "错误:指定的JDK路径不存在!路径:${JAVA_HOME}"
exit 1
fi
# 验证JDK版本
echo "当前脚本检测的JDK版本:"
${JAVA_HOME}/bin/java -version # 直接用绝对路径验证,避免环境变量干扰
echo "------------------------"
# ###########################
# 入参检查:必须传入端口号
# ###########################
if [ $# -ne 1 ]; then
echo "用法:$0 <端口号>(如:./start.sh 8080)"
exit 1
fi
PORT=$1
# 校验端口合法性
if ! [[ "${PORT}" =~ ^[0-9]+$ ]] || [ "${PORT}" -lt 1 ] || [ "${PORT}" -gt 65535 ]; then
echo "错误:端口号必须是1-65535之间的数字!"
exit 1
fi
# ###########################
# 目录/文件检查
# ###########################
if [ ! -d "${APP_BASE_DIR}" ]; then
echo "错误:应用基础目录不存在!路径:${APP_BASE_DIR}"
exit 1
fi
if [ ! -f "${JAR_PATH}" ]; then
echo "错误:JAR包不存在!路径:${JAR_PATH}"
exit 1
fi
if [ ! -f "${CONF_PATH}" ]; then
echo "错误:配置文件不存在!路径:${CONF_PATH}"
exit 1
fi
# 日志目录检查&权限设置
LOG_DIR="${APP_BASE_DIR}/logs"
if [ ! -d "${LOG_DIR}" ]; then
mkdir -p "${LOG_DIR}"
if id "zoc-app" >/dev/null 2>&1; then
chown -R zoc-app:zoc-app "${LOG_DIR}"
chmod 755 "${LOG_DIR}"
fi
fi
LOG_FILE="${LOG_DIR}/${APP_NAME}-${PORT}.out"
PID_FILE="${LOG_DIR}/${APP_NAME}-${PORT}.pid"
# ###########################
# 启动应用(核心修复:用JDK 17绝对路径)
# ###########################
echo "启动${APP_NAME}实例(端口${PORT})..."
cd "${APP_BASE_DIR}" || exit 1
# 核心修复:直接调用JDK 17的java绝对路径,不受sudo环境变量影响
if id "zoc-app" >/dev/null 2>&1; then
sudo -u zoc-app nohup ${JAVA_HOME}/bin/java ${JVM_OPTS} \
-jar ${JAR_PATH} \
--spring.config.location=${CONF_PATH} \
--server.port=${PORT} > "${LOG_FILE}" 2>&1 &
else
nohup ${JAVA_HOME}/bin/java ${JVM_OPTS} \
-jar ${JAR_PATH} \
--spring.config.location=${CONF_PATH} \
--server.port=${PORT} > "${LOG_FILE}" 2>&1 &
fi
# 检查启动是否成功
START_PID=$!
sleep 3
if ps -p ${START_PID} >/dev/null 2>&1; then
echo ${START_PID} > "${PID_FILE}"
if id "zoc-app" >/dev/null 2>&1; then
chown zoc-app:zoc-app ${PID_FILE}
fi
echo "启动成功!PID:${START_PID},日志:${LOG_FILE}"
else
echo "启动失败!日志前10行:"
head -n 10 "${LOG_FILE}"
exit 1
fi
cat > /opt/java-app/zoc/bin/stop.sh << 'EOF'
#!/bin/bash
APP_NAME="zoc"
APP_BASE_DIR="/opt/java-app/zoc"
if [ $# -ne 1 ]; then
echo "用法:$0 <端口号>(如:./stop.sh 8080)"
exit 1
fi
PORT=$1
PID_FILE="${APP_BASE_DIR}/logs/${APP_NAME}-${PORT}.pid"
if [ ! -f ${PID_FILE} ]; then
echo "实例未运行"
exit 0
fi
PID=$(cat ${PID_FILE})
# 停止进程
kill ${PID}
sleep 10
if ps -p ${PID} > /dev/null; then
kill -9 ${PID}
fi
rm -f ${PID_FILE}
echo "实例已停止"
EOF
指定jdk版本:
#!/bin/bash
# 核心配置:与启动脚本保持一致的JDK版本路径
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
# 应用基础配置(与start.sh保持一致)
APP_NAME="trafficfruit"
APP_BASE_DIR="/opt/java-app/tf"
# ###########################
# 前置检查:确保JDK路径有效(可选)
# ###########################
if [ ! -d "${JAVA_HOME}" ]; then
echo "警告:指定的JDK路径不存在!路径:${JAVA_HOME}"
echo "(停止进程不依赖JDK,将继续执行停止操作)"
fi
# 临时设置PATH(仅为脚本完整性)
export PATH="${JAVA_HOME}/bin:${PATH}"
# ###########################
# 入参检查:必须传入端口号
# ###########################
if [ $# -ne 1 ]; then
echo "用法:$0 <端口号>(如:./stop.sh 8080)"
exit 1
fi
PORT=$1
# 校验端口合法性(新增:和启动脚本保持一致)
if ! [[ "${PORT}" =~ ^[0-9]+$ ]] || [ "${PORT}" -lt 1 ] || [ "${PORT}" -gt 65535 ]; then
echo "错误:端口号必须是1-65535之间的数字!你输入的是:${PORT}"
exit 1
fi
# ###########################
# 进程文件检查与验证(生产环境优化)
# ###########################
PID_FILE="${APP_BASE_DIR}/logs/${APP_NAME}-${PORT}.pid"
if [ ! -f ${PID_FILE} ]; then
# 优化:PID文件不存在时,尝试通过端口查找进程
echo "未找到PID文件:${PID_FILE}"
# 查找占用指定端口的Java进程(优化:兼容ss/netstat,且用JDK绝对路径过滤更精准)
PID=""
if command -v ss >/dev/null 2>&1; then
PID=$(ss -tulpn 2>/dev/null | grep -E ":${PORT}\s+" | grep -E "${JAVA_HOME}/bin/java|java" | awk '{print $7}' | cut -d'=' -f2 | cut -d',' -f1)
elif command -v netstat >/dev/null 2>&1; then
PID=$(netstat -tulpn 2>/dev/null | grep -E ":${PORT}\s+" | grep -E "${JAVA_HOME}/bin/java|java" | awk '{print $7}' | cut -d'=' -f2 | cut -d',' -f1)
fi
if [ -z "${PID}" ]; then
echo "实例(端口${PORT})未运行"
exit 0
else
echo "通过端口找到进程PID:${PID}"
fi
else
PID=$(cat ${PID_FILE})
# 验证PID是否为有效进程
if ! ps -p ${PID} > /dev/null 2>&1; then
echo "PID文件中的进程(${PID})已不存在,清理无效PID文件"
rm -f ${PID_FILE}
exit 0
fi
fi
# ###########################
# 优雅停止进程(生产环境标准流程)
# ###########################
echo "停止${APP_NAME}实例(端口${PORT},PID:${PID})..."
# 第一步:发送优雅停止信号(SIGTERM)
kill ${PID} > /dev/null 2>&1
# 等待进程退出(最多30秒,避免强制杀进程)
WAIT_TIME=0
MAX_WAIT=30
while ps -p ${PID} > /dev/null 2>&1; do
if [ ${WAIT_TIME} -ge ${MAX_WAIT} ]; then
echo "等待${MAX_WAIT}秒后进程仍未退出,强制终止(kill -9)"
kill -9 ${PID} > /dev/null 2>&1
break
fi
echo "等待进程退出...(已等待${WAIT_TIME}秒)"
sleep 1
WAIT_TIME=$((WAIT_TIME + 1))
done
# 清理PID文件
if [ -f ${PID_FILE} ]; then
rm -f ${PID_FILE}
echo "已清理PID文件:${PID_FILE}"
fi
# 最终验证
if ps -p ${PID} > /dev/null 2>&1; then
echo "停止失败!进程${PID}仍在运行"
exit 1
else
echo "实例(端口${PORT})已成功停止"
exit 0
fi
#!/bin/bash
# 查看所有运行中的实例状态
APP_NAME="zoc"
APP_BASE_DIR="/opt/java-app/zoc"
PID_FILES="${APP_BASE_DIR}/logs/${APP_NAME}-*.pid"
echo "${APP_NAME} 运行中实例:"
echo "----------------------------------------"
for pid_file in ${PID_FILES}; do
if [ -f ${pid_file} ]; then
# 提取端口号
PORT=$(echo ${pid_file} | grep -oP "(?<=-)\d+(?=\.pid)")
PID=$(cat ${pid_file})
# 检查进程是否存活
if ps -p ${PID} > /dev/null 2>&1; then
echo "端口:${PORT} | PID:${PID} | 状态:运行中"
else
echo "端口:${PORT} | PID:${PID} | 状态:已停止(清理无效PID)"
rm -f ${pid_file}
fi
fi
done
echo "----------------------------------------"
指定jdk:
#!/bin/bash
# 核心配置:与启动/停止脚本保持一致的JDK版本路径(仅为配置统一)
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
# 应用基础配置(关键修改:对齐trafficfruit项目)
APP_NAME="trafficfruit" # 原zoc → 改为trafficfruit
APP_BASE_DIR="/opt/java-app/tf" # 原/opt/java-app/zoc → 改为/opt/java-app/tf
PID_FILES="${APP_BASE_DIR}/logs/${APP_NAME}-*.pid"
# ###########################
# 前置检查:JDK路径验证(仅为配置完整性)
# ###########################
if [ ! -d "${JAVA_HOME}" ]; then
echo "⚠️ 警告:指定的JDK路径不存在!路径:${JAVA_HOME}"
echo "(状态检查不依赖JDK,将继续执行)"
echo "----------------------------------------"
else
# 临时设置PATH(保持配置统一)
export PATH="${JAVA_HOME}/bin:${PATH}"
# 可选:显示当前配置的JDK版本(改用绝对路径,避免sudo环境干扰)
echo "当前配置的JDK版本:"
${JAVA_HOME}/bin/java -version 2>&1 | head -n 1
echo "----------------------------------------"
fi
# ###########################
# 实例状态检查(生产环境优化)
# ###########################
echo "${APP_NAME} 运行中实例:"
echo "----------------------------------------"
# 标记是否有运行中的实例
has_running=0
# 遍历所有PID文件
for pid_file in ${PID_FILES}; do
# 跳过非文件类型(如无匹配的情况)
[ -f "${pid_file}" ] || continue
# 提取端口号(兼容不同命名格式,增强鲁棒性)
PORT=$(basename "${pid_file}" | sed -n "s/${APP_NAME}-\([0-9]\+\)\.pid/\1/p")
if [ -z "${PORT}" ]; then
echo "❌ 无效PID文件:${pid_file}(无法提取端口号),已清理"
rm -f "${pid_file}"
continue
fi
PID=$(cat "${pid_file}" 2>/dev/null)
# 检查PID是否有效
if [ -z "${PID}" ] || ! [[ "${PID}" =~ ^[0-9]+$ ]]; then
echo "❌ 端口:${PORT} | PID:无效 | 状态:PID文件损坏(清理无效文件)"
rm -f "${pid_file}"
continue
fi
# 检查进程是否存活
if ps -p "${PID}" > /dev/null 2>&1; then
# 额外优化:精准匹配JDK 17的Java进程(避免误判)
if ps -p "${PID}" -o cmd= | grep -E "${JAVA_HOME}/bin/java|java" > /dev/null 2>&1; then
echo "✅ 端口:${PORT} | PID:${PID} | 状态:运行中"
has_running=1
else
echo "❌ 端口:${PORT} | PID:${PID} | 状态:非目标Java进程(清理无效PID)"
rm -f "${pid_file}"
fi
else
echo "❌ 端口:${PORT} | PID:${PID} | 状态:已停止(清理无效PID)"
rm -f "${pid_file}"
fi
done
# 无实例时的友好提示
if [ ${has_running} -eq 0 ]; then
echo "ℹ️ 暂无运行中的${APP_NAME}实例"
fi
echo "----------------------------------------"
# 赋予执行权限
sudo chmod 750 /opt/java-app/zoc/bin/{start.sh,stop.sh,status.sh}
sudo chown zoc-app:zoc-app /opt/java-app/zoc/bin/{start.sh,stop.sh,status.sh}
# 启动端口8080的实例
sudo /opt/java-app/zoc/bin/start.sh 8080
# 启动端口8081的实例
sudo /opt/java-app/zoc/bin/start.sh 8081
# 停止端口8080的实例
sudo /opt/java-app/zoc/bin/stop.sh 8080
ps -ef | grep zoc.jar | grep -v grep
- 日志切割:配置
logrotate 定期切割日志;
- 进程监控:搭配
supervisor 实现进程自动重启;
- 负载均衡:通过 Nginx 反向代理多实例。