流水账债务

现在基本可以锁定问题了。

你这个 run.sh 有两个高危点:

sh 复制代码
#! /bin/sh

以及:

sh 复制代码
nohup java ... > "$DAEMON_OUT" 2>&1 < /dev/null &

在:

text 复制代码
mac IDEA -> SSH Exec -> /bin/sh -> nohup

链路下,Ubuntu/Debian 的 /bin/sh 实际是:

text 复制代码
dash

而不是 bash。

dash + nohup + 非交互 SSH channel 对 UTF-8/stdout 处理兼容性很差。

这也是为什么:

  • 手工 SSH 登录执行正常
  • IDEA 远程执行乱码

因为:

text 复制代码
手工登录 = interactive bash
IDEA执行 = non-interactive dash

完全不是一个环境。


你现在应该这样改(核心修复)

第一处:改 shebang

把:

sh 复制代码
#! /bin/sh

改成:

bash 复制代码
#!/bin/bash -l

这里:

  • bash

    避免 dash 的 locale/stdout 问题

  • -l

    强制 login shell

这是关键。


第二处:显式指定 UTF-8

在变量区增加:

bash 复制代码
export LANG=C.UTF-8
export LC_ALL=C.UTF-8

第三处:JVM 增加编码参数

你现在:

sh 复制代码
JVM_PARAM="-Xms${JVM_MIN} ..."

改成:

bash 复制代码
JVM_PARAM="\
-Xms${JVM_MIN} \
-Xmx${JVM_MAX} \
-XX:+ExitOnOutOfMemoryError \
-Dfile.encoding=UTF-8 \
-Dsun.jnu.encoding=UTF-8 \
-Dspring.cloud.config.uri=${CONFIG_URI} \
-Dspring.profiles.active=${RUN_TYPE} \
-DNODEID=${NODE_ID}"

第四处:nohup 前增加 env(非常关键)

现在:

sh 复制代码
nohup ${JAVA_HOME}/bin/java ...

改成:

bash 复制代码
nohup env LANG=C.UTF-8 LC_ALL=C.UTF-8 \
${JAVA_HOME}/bin/java ${JVM_PARAM} \
-jar ${EXEC_JAR} ${@} \
>> "$DAEMON_OUT" 2>&1 < /dev/null &

注意:

这里我顺便把:

sh 复制代码
>

改成:

sh 复制代码
>>

避免覆盖日志。


为什么你"服务器直接执行正常"

因为:

bash 复制代码
ssh host
./run.sh start

实际上:

text 复制代码
bash login shell

会自动:

bash 复制代码
source /etc/profile
source ~/.bashrc

并继承:

bash 复制代码
TERM=xterm-256color
LANG=C.UTF-8

而 IDEA 的 SSH Exec:

通常是:

bash 复制代码
/bin/sh -c ./run.sh start

甚至:

bash 复制代码
sh run.sh start

所以:

text 复制代码
shebang可能都失效

这是很多人没意识到的点。


再补一个隐藏问题(很重要)

你这里:

sh 复制代码
if [ "$USER" != "$RUN_USER" ];

在 IDEA SSH Exec 下:

text 复制代码
$USER

可能为空。

更安全写法:

bash 复制代码
if [ "$(whoami)" != "$RUN_USER" ]; then

我建议你最终改成这样

bash 复制代码
#!/bin/bash -l

export LANG=C.UTF-8
export LC_ALL=C.UTF-8

NODE_ID="34225"
JAVA_HOME="/home/distcom/software/jdk1.8.0_491"
JVM_MIN="128m"
JVM_MAX="512m"
RUN_USER="distcom"
RUN_TYPE="peer1"
EXEC_JAR="openservices-spring-boot-starter.jar"
LOG_FILE="server.log"
CONFIG_URI="http://192.168.1.192:8040/"

if [ "$(whoami)" != "$RUN_USER" ]; then
  echo "failed, It can only be executed under $RUN_USER"
  exit 0
fi

JVM_PARAM="\
-Xms${JVM_MIN} \
-Xmx${JVM_MAX} \
-XX:+ExitOnOutOfMemoryError \
-Dfile.encoding=UTF-8 \
-Dsun.jnu.encoding=UTF-8 \
-Dspring.cloud.config.uri=${CONFIG_URI} \
-Dspring.profiles.active=${RUN_TYPE} \
-DNODEID=${NODE_ID}"

NODE_PATH=$(cd `dirname $0`; pwd)

LOG_PATH="${NODE_PATH}/log"
mkdir -p "$LOG_PATH"

LOG_FILE="${LOG_PATH}/${LOG_FILE}"
DAEMON_OUT="${LOG_PATH}/stdout.out"

DATA_PATH="${NODE_PATH}/data"
mkdir -p "$DATA_PATH"

PIDFILE="${DATA_PATH}/server.pid"

case $1 in
start)

echo -n "Starting ... "

if [ -f "$PIDFILE" ]; then
   if kill -0 `cat "$PIDFILE"` > /dev/null 2>&1; then
      echo already running as process `cat "$PIDFILE"`
      exit 0
   fi
fi

if [ -e "$LOG_FILE" ]; then
   mv "$LOG_FILE" "${LOG_FILE}.`date +%Y%m%d%H%M`"
fi

if [ -e "$DAEMON_OUT" ]; then
   mv "$DAEMON_OUT" "${DAEMON_OUT}.`date +%Y%m%d%H%M`"
fi

shift

nohup env LANG=C.UTF-8 LC_ALL=C.UTF-8 \
${JAVA_HOME}/bin/java \
${JVM_PARAM} \
-jar ${EXEC_JAR} ${@} \
>> "$DAEMON_OUT" 2>&1 < /dev/null &

echo $! > "$PIDFILE"

;;
esac

最后一个关键建议

如果你用 IDEA:

Remote SSH / Deployment

尽量:

不要用:

text 复制代码
sh run.sh start

而是:

text 复制代码
bash -l run.sh start

或者:

text 复制代码
/bin/bash -lc "./run.sh start"

这里:

  • -l

    login shell

  • -c

    command mode

这是 Linux 运维里标准远程执行姿势。


你的问题本质上不是 Java 编码问题

而是:

text 复制代码
IDEA SSH 非交互 shell
+
dash
+
nohup
+
stdout

组合导致的字符流能力下降。

简单处理直接在run.sh 加上export LANG=zh_CN.UTF-8 export LC_ALL=zh_CN.UTF-8 即可 其他不需修改。

对,从你现在反馈的现象来看:

text 复制代码
手工执行正常
IDEA SSH 远程执行乱码

说明本质上不是:

  • JVM 编码问题
  • 日志框架问题
  • 文件编码问题

而是:

text 复制代码
IDEA 远程执行时缺少 locale 环境继承

所以你现在这个最小修复:

bash 复制代码
export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8

直接放到 run.sh 顶部,确实就足够了。

例如:

bash 复制代码
#! /bin/sh

export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8

这会让:

text 复制代码
nohup -> java -> stdout

整条链路强制使用 UTF-8 locale。

对于你当前环境:

  • Ubuntu
  • 已安装 zh_CN.UTF-8 locale
  • java file.encoding=UTF-8
  • 手工启动正常

这已经能解决问题。


你这个案例也说明:

text 复制代码
问题并不在 JVM
而在 SSH 非交互执行环境

因为:

bash 复制代码
locale

是用户登录后看到的。

但:

text 复制代码
IDEA SSH Exec

未必继承这些变量。

所以:

bash 复制代码
export LANG=...

放在脚本内部最稳。


不过还有一个小建议:

虽然你现在验证 zh_CN.UTF-8 可以解决,但最好还是顺手加一个:

bash 复制代码
-Dfile.encoding=UTF-8

因为:

  • JDK8 某些环境仍会参考 locale 推断编码
  • nohup/daemon 场景有时会丢 locale
  • 后续迁移 Docker/systemd 时更稳定

即:

bash 复制代码
JVM_PARAM="${JVM_PARAM} -Dfile.encoding=UTF-8"

这是长期运维里比较保险的做法。

相关推荐
普修罗双战士2 小时前
专业Markdown转HTML工具类:修复优化与Spring Boot适配
windows·spring boot·html
阿维的博客日记3 小时前
传统 Spring XML 配置 vs Spring Boot Starter 对比文档
xml·spring boot·spring
代码漫谈3 小时前
JVM 参数调优:Spring Boot与JDK新特性的最佳结合
java·jvm·spring boot
北风朝向3 小时前
springboot使用@Validated校验List接口参数
spring boot·后端·list·校验·valid
海兰4 小时前
【第39篇】spring-ai-alibaba-graph-example学习路径概览
人工智能·spring boot·学习·spring·spring ai
Don.TIk4 小时前
天机の学堂
java·spring boot·spring·maven·mybatis
Devin~Y4 小时前
大厂Java面试实录:Spring Boot/JPA/Redis/Kafka/K8s 可观测性 + Spring AI RAG/Agent(小Y翻车现场)
java·spring boot·redis·mybatis·hibernate·spring mvc·jpa
SuperherRo4 小时前
服务攻防-开发框架安全&ThinkPHP&Laravel&SpringBoot&Struts2&SpringCloud&复现
spring boot·laravel·thinkphp·struts2·框架安全
消失的旧时光-19437 小时前
SQL 第五篇:SQL 如何真正接入 Spring Boot 项目(企业 Mapper 分层实战)
数据库·spring boot·sql