如何将Java程序变为守护进程?

75

我有一个Java程序,想要在Linux系统上将其守护化。也就是说,在终端启动该程序后,即使我退出登录,它仍然可以持续运行。同时,我也想能够优雅地停止这个程序。

我找到了这篇文章,其中介绍了通过Shell脚本和Java代码的组合来实现守护化的方法。这看起来不错,但如果可能的话,我希望使用更简单的方法。

你最喜欢在Linux系统上将Java程序守护化的方法是什么?


请查看 https://dev59.com/F0bRa4cB1Zd3GeqPxSC6。 - Dana the Sane
9
尝试使用nohup java prog & - ceving
一种更加简洁的UNIX服务管理方式 https://cr.yp.to/daemontools.html - Velu
可能是重复的问题,参考 https://dev59.com/fmgu5IYBdhLWcg3wnYPo。 - giorgio79
11个回答

37

但是有没有一种方法可以识别系统或进程的停止和启动? - Franz

31
如果您不能依赖 Java Service Wrapper,并且在其他地方引用它(例如,如果您正在运行没有SW打包版本的Ubuntu),那么您可能希望按照传统方式操作:让您的程序将其PID写入 /var/run/$progname.pid,并编写一个标准的SysV init脚本(例如使用ntpd的脚本示例,它很简单)来控制该程序。最好还要使它符合LSB标准。
实际上,启动函数会测试程序是否已经在运行(通过检查 /var/run/$progname.pid 是否存在以及该文件的内容是否为正在运行进程的PID),如果没有则运行。
logfile=/var/log/$progname.log
pidfile=/var/run/$progname.pid
nohup java -Dpidfile=$pidfile $jopts $mainClass </dev/null > $logfile 2>&1

stop函数检查 /var/run/$progname.pid 文件,检测该文件是否为正在运行的进程的PID,验证它是否为Java VM(以免杀死仅重新使用已死亡的Java守护程序实例的PID的进程),然后杀死该进程。

当调用时,我的main()方法将首先在System.getProperty("pidfile")中定义的文件中写入其PID。

但是,有一个主要障碍:在Java中,没有简单和标准的方法来获取JVM运行的进程的PID。

以下是我想到的解决方法:

private static String getPid() {
    File proc_self = new File("/proc/self");
    if(proc_self.exists()) try {
        return proc_self.getCanonicalFile().getName();
    }
    catch(Exception e) {
        /// Continue on fall-back
    }
    File bash = new File("/bin/bash");
    if(bash.exists()) {
        ProcessBuilder pb = new ProcessBuilder("/bin/bash","-c","echo $PPID");
        try {
            Process p = pb.start();
            BufferedReader rd = new BufferedReader(new InputStreamReader(p.getInputStream()));
            return rd.readLine();
        }
        catch(IOException e) {
            return String.valueOf(Thread.currentThread().getId());
        }
    }
    // This is a cop-out to return something when we don't have BASH
    return String.valueOf(Thread.currentThread().getId());
}

9
不需要使用Java来编写PID,脚本也可以做到(echo $! > /var/run/my.pid)。此外,由于通常只有root用户才能写入/var/run目录,因此您可以将Shell脚本作为root用户运行,将守护程序作为另一个用户运行。 - David Rabinowitz
除非您使用Java,否则无法更改uid或euid,因此您必须在shell中使用sudo进行更改...如果您问我,这有点麻烦。将/var/run和/var/log对您运行的用户可写会更容易些... - Varkhan
3
Java编写pid文件/锁文件的理由是为了有一种方法来知道守护进程的初始化阶段是否成功完成(通过在该阶段末尾进行写入)。 - Varkhan
1
这个回答没用。你需要在创建套接字之后再进行分叉。这只是将进程从 shell 中分离出来,完全是另一回事。 - LtWorf
退缩的选项行不通——线程 ID 几乎肯定不同于进程 ID。 - Luke Hutchison
在这种情况下使用nohup是一个非常糟糕的想法。 - symcbean

25

我经常编写类似下面这样的脚本或命令行,如果我想要:

  1. 运行一个免受sighup影响的程序
  2. 该程序完全与生成它的shell断开连接,并且
  3. 从stderr和stdout产生一个日志文件,其内容也被显示出来,但是
  4. 允许我停止查看正在进行的日志并做其他事情而不会中断正在运行的进程

享受吧。

nohup java com.me.MyProgram </dev/null 2>&1 | tee logfile.log &

1
你可以使用cron和@reboot功能来启动它。 - Sven
但这意味着每次想要启动服务时都需要重新启动。像这样使用“nohup”是一个非常糟糕的主意。 - symcbean

14

我更喜欢使用nohup命令。这篇博客文章中提到了更好的方法,但我认为它们并没有更好到足以改变我的选择。


它并不完全是守护进程化的...如果操作系统重新启动会怎么样?这里有一个更干净的解决方案,使用daemontools:https://dev59.com/questions/xXRB5IYBdhLWcg3wuZbo#35569052 - Velu
1
系统重启或关闭后,此服务将不会重新启动! - Velu

5

3
你如何知道它是否满足他的需求?根据其许可证,Java Service Wrapper在商业使用时并非免费的:http://wrapper.tanukisoftware.com/doc/english/licenseOverview.html - Jared

5

在Ubuntu上,我更喜欢使用libslack的“daemon”实用程序。这也是Jenkins在Ubuntu上使用的方式(这就是我得到这个想法的地方)。我已经将其用于基于Jetty的服务器应用程序,并且它运行良好。

当您停止daemon进程时,它会向JVM发出关闭信号。您可以通过使用Runtime.addShutdownHook()注册一个关闭钩子,在此时执行关闭/清理代码。


4

这要看情况。如果只是一次性的事情,我想将其守护进程化然后回家,但通常我会等待结果,可能会执行以下操作:

nohup java com.me.MyProgram &

在命令行下面,要清除它,你有很多选项。你可以监听SIGKILL,或者监听一个端口,在建立连接时关闭,定期检查一个文件。不同的方法有不同的弱点。如果用于生产环境,我会认真考虑,并将脚本放入/etc/init.d中,以nohup方式运行,并采用像tomcat一样更复杂的关机方式。


是的,它并不完全是守护进程化的...如果操作系统重新启动会怎样?这里有一个更干净的解决方案,使用daemontools https://dev59.com/xXRB5IYBdhLWcg3wuZbo#35569052 - Velu

3
DaemonTools:UNIX系统服务管理的一种更简洁的方式 https://cr.yp.to/daemontools.html
  1. https://cr.yp.to/daemontools/install.html安装daemon工具,按照说明进行操作,如遇问题,请尝试使用https://gist.github.com/rizkyabdilah/8516303中的说明。

  2. /etc/init/svscan.conf创建一个文件,并添加以下几行(仅适用于cent-os-6.7)

 start on runlevel [12345]
 stop on runlevel [^12345]
 respawn
 exec /command/svscanboot
在 /service/vm/ 文件夹内创建一个名为 run 的新脚本,并添加以下内容。
#!/bin/bash    
echo starting VM 
exec java -jar
/root/learning-/daemon-java/vm.jar
注意: 用您自己的Jar文件替换此处的Jar文件,或者替换任何Java类文件。
4. 重启系统。 5. 执行命令"svstat /service/vm"应该现在已经运行起来了! 6. 执行命令"svc -d /service/vm"应该能够关闭vm服务。 7. 执行命令"svc -u /service/vm"应该能够启动vm服务。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接