很多开发者都遇到过这样的场景:你开发了一个带图形界面的Java应用,比如数据监控面板或者自动化工具,需要它24小时在后台运行。但用户一关机或者注销,程序就跟着退出了。更麻烦的是,每次开机还得手动双击图标启动,这对于需要长期运行的业务系统简直是噩梦。
Windows服务(Windows Service)就是为了解决这类问题而生的。它能在没有用户登录的情况下自动运行,支持开机自启,还能通过服务管理器统一管理。但Java程序默认不具备这种能力,这时候Apache Commons Daemon的Procrun组件就成了救命稻草。
我去年给客户部署过一个生产环境的数据采集系统,就是用Swing开发的GUI程序。最初让用户自己维护运行时经常出问题,后来改用服务化部署后,稳定性直接提升了好几个级别。下面我就把踩坑总结的经验完整分享给你。
在开始之前,确保你的开发环境满足这些条件:
特别提醒:Procrun对32/64位系统有要求。如果你的Java是64位的,记得使用amd64目录下的prunsrv.exe,否则会遇到莫名其妙的兼容性问题。
建议采用这样的目录结构:
code复制/service
/bin # 存放prunsrv.exe和prunmgr.exe
/conf # 服务配置文件
/logs # 日志目录
/lib # 依赖的jar包
app.jar # 主程序
install.bat # 安装脚本
这种结构清晰隔离了可执行文件、配置和日志,后期维护会方便很多。我曾经在一个项目里把所有文件堆在同一个目录,升级时差点酿成事故。
关键点在于实现Daemon接口,这是服务能正常启停的核心。看这个示例:
java复制import org.apache.commons.daemon.Daemon;
import org.apache.commons.daemon.DaemonContext;
public class MyService implements Daemon {
private Thread workerThread;
@Override
public void init(DaemonContext context) {
// 初始化操作
System.out.println("Initializing...");
}
@Override
public void start() {
// 启动工作线程
workerThread = new Thread(() -> {
while (!Thread.interrupted()) {
// 你的业务逻辑
}
});
workerThread.start();
}
@Override
public void stop() {
// 优雅停止
workerThread.interrupt();
}
@Override
public void destroy() {
// 清理资源
}
}
注意:如果你的GUI用了JavaFX或Swing,需要特别处理事件调度线程(EDT)。我遇到过因为没正确处理EDT导致服务无法停止的情况。
安装服务的核心是prunsrv命令,这里给出一个带详细注释的bat脚本示例:
bat复制@echo off
set SERVICE_NAME=MyJavaService
set PR_INSTALL=%~dp0bin\prunsrv.exe
set APP_HOME=%~dp0
%PR_INSTALL% //IS//%SERVICE_NAME% ^
--DisplayName="My Java Service" ^
--Description="数据采集服务v1.0" ^
--Startup=auto ^
--Classpath=%APP_HOME%lib\app.jar;%APP_HOME%lib\*.jar ^
--Jvm=auto ^
--StartMode=jvm ^
--StartClass=com.example.MyService ^
--StartMethod=start ^
--StopMode=jvm ^
--StopClass=com.example.MyService ^
--StopMethod=stop ^
--LogPath=%APP_HOME%logs ^
--LogLevel=Info ^
--StdOutput=auto ^
--StdError=auto
参数说明:
//IS 表示安装服务--Startup=auto 设置开机自启--Jvm=auto 自动检测JVM路径StdOutput 建议设为auto方便调试如果你的服务需要MySQL或Redis先启动,可以用++DependsOn参数:
bat复制++DependsOn=MySQL#Redis
注意服务名要和在服务管理器里显示的完全一致。我曾经因为大小写问题排查了整整一个下午。
Java服务长时间运行容易出现内存问题。建议在JvmOptions里添加这些参数:
bat复制++JvmOptions=-Xmx1024m;-Xms256m;-XX:+HeapDumpOnOutOfMemoryError;-XX:HeapDumpPath=%APP_HOME%logs
当出现OOM时会自动生成dump文件。有次客户现场出现内存泄漏,就是靠这个dump文件找到了是缓存没清理的问题。
| 错误代码 | 含义 | 解决方案 |
|---|---|---|
| 1053 | 服务启动超时 | 检查start方法是否阻塞主线程 |
| 1064 | 服务交互式错误 | 检查是否缺少--Type=interactive |
| 2 | 文件未找到 | 检查Classpath路径是否正确 |
遇到问题时,先查看logs目录下的日志文件,90%的问题都能从这里找到线索。
直接运行prunmgr.exe,输入服务名就能看到实时状态:

这个工具可以修改服务参数、查看实时日志,比Windows自带的服务管理器好用多了。特别提醒:修改配置后要点"Refresh"按钮才会生效,这个坑我踩过三次。
除了图形界面,这些命令也很实用:
prunsrv //ES//MyJavaServiceprunsrv //TS//MyJavaService (控制台模式,方便查看日志)prunsrv //DS//MyJavaService建议把这些命令写成bat脚本,交给运维人员使用。我在项目交付时都会准备完整的运维手册。
去年给某银行做的报表系统就是个典型例子。原程序是用Swing开发的客户端,改造时遇到了这些特殊问题:
最终改造后的服务已经稳定运行了200多天,客户反馈系统可用性从原来的90%提升到了99.9%。