用 WinSW 把 JAR 包注册成 Windows 服务
在 Windows 服务器上部署 Java 应用,很多人的做法是写一个 .bat 脚本,然后让运维手动双击运行。这种方式有几个明显的问题:
- 服务器重启后需要手动再次启动
- 没有守护进程,程序崩溃后不会自动恢复
- 日志管理混乱,输出全靠
System.out.println - 需要保持一个命令行窗口常驻
WinSW(Windows Service Wrapper)解决的就是这些问题。它让任何可执行程序都能注册成 Windows 服务,自带开机自启、崩溃重启、日志滚动等能力,而且完全开源免费。

WinSW 的核心思路是:由 Windows 服务管理器托管
winsw.exe,再由 WinSW 启动并守护你的javaw -jar app.jar应用。
技术背景
Windows 服务是什么
Windows 服务(Windows Service)是一种在后台长期运行的进程,由系统服务管理器(SCM)统一管理。它的特点:
- 开机自动启动,无需用户登录
- 独立于用户会话,不依赖任何登录的用户
- 可配置故障恢复,崩溃后自动重启
- 统一管理入口:任务管理器、
services.msc、sc命令都可以控制
WinSW 做了什么
WinSW 本质上是一个”包装器”。它把你的程序(比如 java -jar app.jar)包装成一个 Windows 服务进程,让 SCM 可以像管理系统服务一样管理你的应用。
整个结构很简单:
1 | |
WinSW 负责与 Windows SCM 通信,你的 JAR 负责跑业务。
java 和 javaw 的区别
启动 JAR 时可以选择 java 或 javaw,两者的核心区别是:
java |
javaw |
|
|---|---|---|
| 控制台窗口 | 会弹出黑色命令行窗口 | 不弹出窗口,静默运行 |
| 标准输出 | 输出到控制台 | 无控制台,输出由 WinSW 捕获写入日志 |
| 适用场景 | 调试、命令行工具 | Windows 服务、GUI 程序(推荐) |
在 WinSW 管理的 Windows 服务里,程序运行在系统后台,根本不需要控制台窗口。用 javaw 既干净又符合服务程序的运行方式,所有输出都由 WinSW 接管并写入 .out.log / .err.log 文件。
javaw在 JDK 的bin目录下,与java.exe并列,路径相同,只是换个文件名。
准备工作
环境要求
- Windows Server 2012 R2 及以上,或 Windows 10/11
- .NET Framework 4.6.1 及以上(大多数 Windows 已内置)
- JDK 已安装并配置好
JAVA_HOME(或直接用完整路径)
下载 WinSW
WinSW 发布在 GitHub:https://github.com/winsw/winsw/releases
下载页面有多个版本,按需选择:
| 文件名 | 适用场景 |
|---|---|
WinSW-x64.exe |
64 位系统(主流选择) |
WinSW-x86.exe |
32 位系统 |
WinSW-arm64.exe |
ARM 架构系统 |
下载后得到一个单独的 .exe 文件,无需安装,拷贝到目标目录即可使用。
目录结构规划
建议把每个服务独立放在一个目录下,结构清晰,便于维护:
1 | |
命名规则很重要:WinSW 的
.exe和.xml文件必须同名(不含扩展名),WinSW 会自动查找同目录下同名的 XML 配置文件。
实际操作:把下载的 WinSW-x64.exe 重命名为 my-app.exe,然后在同目录下创建 my-app.xml。
编写配置文件
my-app.xml 是整个部署的核心,它告诉 WinSW 如何启动你的程序。
基础配置(最小可运行版本)
1 | |
这个配置已经可以运行。下面是生产环境推荐的完整配置:
完整配置(生产推荐)
1 | |
配置项详解
<executable> 和 <arguments>
executable 填可执行程序的路径(或环境变量中存在的命令名,如 java)。如果系统 PATH 里没有 java,需要写完整路径:
1 | |
arguments 里的参数换行写更清晰,WinSW 会自动拼接。
<onfailure> 故障恢复
三条 onfailure 对应三次故障后的动作,按顺序触发:第一次崩溃 10 秒后重启,第二次 20 秒后重启,第三次 30 秒后重启。resetfailure 表示 1 小时内没有崩溃则重置计数器。
<log mode> 支持四种模式:
| 模式 | 说明 |
|---|---|
append |
追加到同一文件(默认) |
reset |
每次启动时清空日志 |
roll |
按大小滚动 |
roll-by-size-time |
按大小和时间双重滚动(推荐生产使用) |
注册和管理服务
所有操作都需要以管理员权限打开命令提示符(CMD)或 PowerShell。
注册服务
1 | |
成功后会看到:
1 | |
启动服务
1 | |
或者用系统命令:
1 | |
停止服务
1 | |
或者:
1 | |
重启服务
1 | |
查看服务状态
1 | |
输出示例:
1 | |
或者通过 SC 查看详情:
1 | |
卸载服务
先停止服务,再卸载:
1 | |
图形界面管理
按 Win + R 输入 services.msc,打开服务管理器,找到 “My App Service”,可以直接右键启动/停止/重启,也可以修改启动类型。
日志说明
WinSW 运行后会在 <logpath> 目录下生成三类日志文件:
1 | |
排查问题时,先看 wrapper.log 确认服务是否正常启动,再看 err.log 查应用异常。
Spring Boot 应用通常自带日志框架(Logback/Log4j2),建议在 application.yml 里也配置日志文件路径,两套日志各司其职:
- WinSW 日志:记录服务生命周期事件(启动/停止/崩溃/重启)
- 应用日志:记录业务逻辑和异常
实战示例:部署 Spring Boot 应用
假设有一个打包好的 user-service-1.0.0.jar,需要部署到 D:\services\user-service\。
第一步:创建目录并放置文件
1 | |
第二步:编写配置文件
1 | |
第三步:以管理员身份注册并启动
1 | |
第四步:验证服务运行
1 | |
升级版本(替换 JAR)
1 | |
如果新版本 JAR 改了文件名,也需要同步更新 XML 里的
<arguments>路径。
常见问题排查
服务注册失败:拒绝访问
现象: install 时提示 “Access is denied”
原因: 没有以管理员身份运行
解决: 右键命令提示符 → “以管理员身份运行”,再执行命令
服务启动后立即停止
现象: start 后状态马上变回 Stopped
排查步骤:
- 查看
logs\my-app.wrapper.log,找ERROR关键字 - 查看
logs\my-app.err.log,看 Java 异常信息 - 常见原因:
- JAR 路径写错(注意反斜杠和空格)
- 端口被占用:
netstat -ano | findstr 8080 - JVM 参数不正确(内存设置超出系统可用内存)
- 依赖的配置文件路径不对
javaw 命令找不到
现象: wrapper.log 里提示找不到 javaw
解决: 在 XML 里用 Java 的完整路径:
1 | |
或者在 XML 里补充 PATH 环境变量:
1 | |
中文路径或中文日志乱码
现象: 日志文件里中文显示为乱码
解决: 在 <arguments> 里加上编码参数:
1 | |
同时确保 XML 文件本身以 UTF-8 编码保存,第一行声明 encoding="UTF-8"。
卸载时提示服务正在运行
先停止再卸载:
1 | |
如果还是卸载失败,用 SC 命令强制删除:
1 | |
小结
WinSW 的核心就三个文件:一个 EXE、一个 XML、一个 JAR。配置逻辑也很清晰——XML 里告诉 WinSW 怎么启动你的程序,剩下的事情交给 Windows 服务管理器。
用一张表总结常用命令:
| 操作 | WinSW 命令 | 系统命令 |
|---|---|---|
| 注册服务 | my-app.exe install |
— |
| 启动服务 | my-app.exe start |
net start my-app |
| 停止服务 | my-app.exe stop |
net stop my-app |
| 重启服务 | my-app.exe restart |
— |
| 查看状态 | my-app.exe status |
sc query my-app |
| 卸载服务 | my-app.exe uninstall |
sc delete my-app |
生产部署时,几个细节值得注意:
- 用完整路径:
executable和arguments里涉及路径的地方,尽量用绝对路径,避免工作目录带来的歧义 - 配置故障恢复:
onfailure是生产环境必备,程序崩溃后自动重启是刚需 - 管理员权限:注册和卸载服务必须在管理员权限下执行
- 日志滚动:生产环境一定要配
roll-by-size-time模式,防止日志把磁盘撑爆