1. 引言

有时候,我们希望在后台运行 Java 程序,即使终端关闭后程序也能继续运行。

2. 将 Java 作为后台进程运行

在后台运行 Java 程序的最简单方法之一是使用 shell 脚本中的 & 操作符:

#!/bin/sh
java -jar /web/server.jar &
echo $! > startupApp.pid

& 确保进程在后台运行,让我们可以在不等待进程完成的情况下继续使用终端。

$! > startupApp.pid 会捕获并存储最近执行的后台命令的 PID(进程 ID)。这个 PID 唯一标识正在运行的进程,可用于后续的进程管理任务,如监控或停止进程。

然而,这种方法有一个缺点。即使我们能继续使用终端,如果关闭终端,进程仍可能被终止。这会导致后台程序意外停止。

此外,由于进程未作为服务管理,很难对其进行有效控制。我们无法像使用 systemd 这样的正规服务管理器那样轻松查看日志或启动/停止进程。

3. 使用 Nohup 保持进程存活

如果我们不希望在关闭会话后进程结束,那么 nohup 就是解决方案。nohup(No Hang Up)可以让我们的进程在关闭会话或终端后继续运行。

以下是使用 nohup 的 shell 脚本:

nohup java -jar /web/server.jar > output.log 2>&1 &
echo $! > startupApp.pid

nohup 会确保进程在关闭终端会话后依然运行。> output.log 2>&1 部分帮助我们将标准输出和错误输出都重定向到一个名为 output.log 的文件中,方便后续查看日志(用于调试目的)。最后的 & 则让进程在后台运行。**

echo $! > startupApp.pid 将 PID 保存到 startupApp.pid 中,以供后续使用。

如果我们想终止进程,可以运行以下命令:

kill $(cat startupApp.pid)

4. 将 Java 作为 systemd 服务运行(适用于 Linux 服务器)

对于长期运行的 Java 进程以及更好的管理,我们需要使用 systemd 将它们作为服务来管理。如果我们使用的是 Linux 服务器,这是推荐的方法。现在让我们来看看如何实现这一点。

首先,我们需要使用以下命令创建一个.service 文件:

sudo nano /etc/systemd/system/myjavaapp.service

接下来,将以下内容添加到文件中:

[Unit]
Description: Java Background Service
After=network.target

[Service]
ExecStart=/usr/bin/java -jar /web/server.jar
WorkingDirectory=/web
Restart=always
User=root
StandardOutput=append:/var/log/myjavaapp.log
StandardError=append:/var/log/myjavaapp.err

[Install]
WantedBy=multi-user.target

我们正在尝试定义如何将 Java 应用程序作为后台服务运行。在这种情况下,我们要确保该应用在启动时自动运行,并且保持持续运行状态。

在 [Unit] 部分中,使用 After=network.target, 我们指定只有在网络可用后才启动。这一点非常重要,尤其是当我们的 Java 应用程序依赖于互联网或网络服务时。

接下来,在 [Service] 部分,我们定义了 Java 应用程序的运行方式。让我们逐一解析。

ExecStart 指示 systemd 通过运行 server.jar 来启动 Java 应用程序。WorkingDirectory 定义了服务将从哪个文件夹执行。在这里,它被设置为 /web 目录。

Restart 确保如果应用程序因任何原因崩溃,systemd 会自动重启该应用程序。

接下来,我们将用户属性设置为 root,这意味着服务将以 root 用户的身份运行。 然而,为了更高的安全性,通常建议以非 root 用户的身份运行服务。

我们还通过 StandOutput 和 StandardError 属性来跟踪日志和错误信息。这些属性对于分析与应用程序相关的问题非常有用。

最后,通过 WantedBy=multi-user.target 属性,我们让 Java 应用程序在系统达到服务正常运行阶段时自动启动,这样服务会在 GUI 渲染之前启动。如果没有这个配置,我们的服务将无法知道何时应该自动启动。它只会处于待机状态,每次重启后我们都必须手动启动它。

接下来,我们需要重新加载 systemd 并启用服务。每当我们创建或修改服务时,都需要这一步,让 systemd 了解这些变更。

我们需要运行以下命令,让 systemd 了解我们新添加(或修改)的服务:

sudo systemctl daemon-reload

接下来,我们需要创建一个指向服务文件的符号链接(快捷方式)。系统会将这个快捷方式放置在一个特殊的文件夹中,在启动时检查它,并在 multi-user.target 阶段启动服务。

以下是创建符号链接的命令:

sudo systemctl enable myjavaapp

现在,要启动 Java 应用程序,我们需要重启系统或者运行以下命令:

sudo systemctl start myjavaapp

我们可以使用以下命令来确认服务是否已启动:

sudo systemctl status myjavaapp

最后,要停止后台进程,我们可以运行以下命令:

sudo systemctl stop myjavaapp

5. 使用 screen 或 tmux 将 Java 作为后台进程运行

与 systemd 或 nohup 不同,screen 和 tmux 允许我们从会话中脱离(断开连接),而不会中断正在运行的进程,之后还能重新连接(重新附着),回到我们离开时的确切位置。

简单来说,使用 screen 或 tmux,我们可以启动一个 Java 应用程序,即使合上笔记本电脑或断开连接,之后回来时仍能看到同样的终端界面,就像什么都没发生过一样,而应用程序会一直在后台运行。

我们可以把它想象成 Windows 的休眠模式,只不过它仅限于终端会话。

我们来使用以下命令:

screen -S myjavaapp java -jar /web/server.jar

要进行分离操作,我们需要按下 Ctrl+A,松开这两个键,然后再按下 D。如果之后想重新连接,可以运行以下命令:

screen -r myjavaapp

我们可以使用 tmux 达到类似的效果。以下是相应的命令:

tmux new-session -s myjavaapp 'java -jar /web/server.jar'

要进行分离操作,我们需要按下 Ctrl+B,松开这两个键,然后按下 D。如果之后想重新连接,可以运行以下命令:

tmux attach-session -t myjavaapp

tmux 更加现代化,功能更强大,使用起来也更方便,而 screen 则简单易用,足以满足基本需求。不过,大多数 Linux 发行版都预装了 screen,而 tmux 可能需要我们单独安装。

6. 总结

在本文中,我们了解了如何将 Java 应用程序作为后台进程运行,探讨了在关闭终端会话后如何保持其运行,甚至还通过日志对其进行监控。

对于简单情况,nohup 或 & 可以起到作用,但在生产环境中,使用系统服务是一种更可靠的方法。像 screen 和 tmux 这样的工具为我们提供了额外的灵活性,即使在重启后也能继续进程,就像什么都没发生过一样。我们需要选择最适合自己使用场景的方法。