以下是我的Java代码:

 Runtime rt = Runtime.getRuntime();
 Process p = rt.exec("nohup java -jar /root/xxxx.jar &");

可以执行这个命令,我可以看到服务已经启动,但我没有看到nohup日志(log),为什么会这样?

推荐答案

Short story

您的期望是基于与Java实际上无关的观察结果,基本上("将输出重定向到nohup.out")不起作用,因为通过java.lang.Runtime#exec产生的进程没有绑定到tty,因此nohup wrapper没有任何影响.

Long story

Q:当UNIX个人想要产生长时间运行的进程/程序时,为什么他们需要使用nohup wrapper

当我们从shell开始工作时,它的前三个文件描述符(stdinstdoutstderr)被绑定到tty,例如:

]# ls -la /proc/self/fd/[0-2]
lrwx------ 1 root root 64 Aug 20 09:29 /proc/self/fd/0 -> /dev/pts/2
lrwx------ 1 root root 64 Aug 20 09:29 /proc/self/fd/1 -> /dev/pts/2
lrwx------ 1 root root 64 Aug 20 09:29 /proc/self/fd/2 -> /dev/pts/2

shell派生的任何进程都继承这三个文件描述符,例如:

# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

...

# another shell
# 65681 - pid of ping
# 65654 - pid of shell (parent)

]# ps -ef | grep ping
root     65681 65654  0 09:31 pts/2    00:00:00 ping 8.8.8.8
root     65700 65684  0 09:32 pts/3    00:00:00 grep --color=auto ping
]# ls -la /proc/65681/fd/[0-2]
lrwx------ 1 root root 64 Aug 20 09:32 /proc/65681/fd/0 -> /dev/pts/2
lrwx------ 1 root root 64 Aug 20 09:32 /proc/65681/fd/1 -> /dev/pts/2
lrwx------ 1 root root 64 Aug 20 09:32 /proc/65681/fd/2 -> /dev/pts/2

Q:如果我们留下shell美元,会发生什么?

]# kill -9 65654
]# ls -la /proc/65681/fd/[0-2]
ls: cannot access /proc/65681/fd/[0-2]: No such file or directory
]# ps -ef | grep 65681
root     65738 65684  0 09:40 pts/3    00:00:00 grep --color=auto 65681

操作系统需要关闭tty(我们离开了shell),所以OS向正在使用(绑定到)该tty的所有进程发送SIGHUP信号并关闭tty

nohup wrapper的目的如下:

它判断前三个文件描述符(stdinstdoutstderr)是否绑定到tty,如果是,则将它们替换为/dev/nullnohup.outnohup.out:

]# nohup ping 8.8.8.8
nohup: ignoring input and appending output to 'nohup.out'

...

# another shell
# 65767 - pid of ping
# 65752 - pid of shell (parent)

]# ps -ef | grep ping
root     65767 65752  0 09:44 pts/2    00:00:00 ping 8.8.8.8
root     65773 65684  0 09:45 pts/3    00:00:00 grep --color=auto ping
]# ls -la /proc/65767/fd/[0-2]
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/0 -> /dev/null
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/1 -> /nohup.out
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/2 -> /nohup.out

现在,试着"离开"我们的shell人:

]# kill -9 65752
]# ls -la /proc/65767/fd/[0-2]
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/0 -> /dev/null
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/1 -> /nohup.out
l-wx------ 1 root root 64 Aug 20 09:45 /proc/65767/fd/2 -> /nohup.out
]# ps -ef | grep ping
root     65767     1  0 09:44 ?        00:00:00 ping 8.8.8.8
root     65781 65684  0 09:46 pts/3    00:00:00 grep --color=auto ping

我们的长时间运行的应用程序继续运行,每个人都很高兴--这是nohup-wrapper的魔力.

关于你的问题...

如果您的目标是使用Java重新实现相同的方法,则需要在Java中执行相同的操作,而不是依赖nohup wrapper,以下是一些示例:

让我们try 使用"朴素"实现,如下所示:

public class Test {

    public static void main(String[] args) throws Exception {
        Runtime rt = Runtime.getRuntime();
        rt.exec("ping 8.8.8.8");
        Thread.sleep(1_000_000_000);
    }
    
}
]# $JAVA_HOME/bin/javac Test.java 
]# $JAVA_HOME/bin/java -cp . Test 

# another shell
# 65890 - pid of ping
# 65873 - pid of java (parent)

]# ps -ef | grep ping
root     65890 65873  0 09:58 pts/2    00:00:00 ping 8.8.8.8
root     65895 65684  0 09:59 pts/3    00:00:00 grep --color=auto ping
]# ls -la /proc/65890/fd/[0-2]
lr-x------ 1 root root 64 Aug 20 09:58 /proc/65890/fd/0 -> pipe:[651007]
l-wx------ 1 root root 64 Aug 20 09:58 /proc/65890/fd/1 -> pipe:[651008]
l-wx------ 1 root root 64 Aug 20 09:58 /proc/65890/fd/2 -> pipe:[651009]

现在,我们看到的不是像"/dev/pt/xxx"这样的文件描述符,而是像pipe:[xxx]这样奇怪的东西.那是什么?也就是说,UNIX IPC-子进程通过anonymous pipe向父进程发送数据.匿名管道不是tty,这就是nohup wrapper不会有任何影响的原因,我们可以演示使用shell(既不是输出中的"nohup.out",也不是"忽略输入并将输出附加到‘nohup.out’"消息):

]# nohup ping 8.8.8.8 </dev/null 2>&1 | cat
^Z
[1]+  Stopped                 nohup ping 8.8.8.8 < /dev/null 2>&1 | cat
]# ps -ef | grep ping
root     66840 65684  0 12:38 pts/3    00:00:00 ping 8.8.8.8
root     66845 65684  0 12:38 pts/3    00:00:00 grep --color=auto ping
]# ls -la /proc/66840/fd/[0-2]
lr-x------ 1 root root 64 Aug 20 12:38 /proc/66840/fd/0 -> /dev/null
l-wx------ 1 root root 64 Aug 20 12:38 /proc/66840/fd/1 -> pipe:[659814]
l-wx------ 1 root root 64 Aug 20 12:38 /proc/66840/fd/2 -> pipe:[659814]

如果父进程退出,会发生什么情况?实际上,这取决于子进程是否通过anonymous pipe发送数据,但在最坏的情况下,子进程将退出:

]# kill 65873
]# ls -la /proc/65890/fd/[0-2]
]# ps -ef | grep ping
root     66238 65684  0 10:31 pts/3    00:00:00 grep --color=auto ping

获得与nohup wrapper类似的行为的唯一可靠方法是在Java端"重新实现"nohup wrapper:

import java.io.File;

public class Test {

    public static void main(String[] args) throws Exception {
        Process process = new ProcessBuilder("ping", "8.8.8.8")
                .directory(new File("/tmp"))
                .redirectInput(new File("/dev/null"))
                .redirectOutput(new File("/tmp/nohup.out"))
                .redirectError(new File("/tmp/nohup.out"))
                .start();
        Thread.sleep(1_000_000_000);
    }

}
]# ps -ef | grep ping
root     66394 66377  0 10:37 pts/2    00:00:00 ping 8.8.8.8
root     66398 65684  0 10:37 pts/3    00:00:00 grep --color=auto ping
]# kill 66377
]# ls -la /proc/66394/fd/[0-2]
lr-x------ 1 root root 64 Aug 20 10:37 /proc/66394/fd/0 -> /dev/null
l-wx------ 1 root root 64 Aug 20 10:37 /proc/66394/fd/1 -> /tmp/nohup.out
l-wx------ 1 root root 64 Aug 20 10:37 /proc/66394/fd/2 -> /tmp/nohup.out
]# ps -ef | grep 66394
root     66394     1  0 10:37 pts/2    00:00:00 ping 8.8.8.8
root     66402 65684  0 10:38 pts/3    00:00:00 grep --color=auto 66394

因此,您的Java实现:

nohup java -jar /root/xxxx.jar &

是:

Process process = new ProcessBuilder("java", "-jar", "/root/xxxx.jar")
        .redirectInput(new File("/dev/null"))
        .redirectOutput(new File("nohup.out"))
        .redirectError(new File("nohup.out"))
        .start();

Java相关问答推荐

OpenJDK、4K显示和文本质量

Java函数式编程中的双值单值映射

无法在org. openjfx:javafx—fxml:21的下列变体之间进行 Select

解析Javadoc时链接的全限定类名

Exe4j创建的应用程序无法再固定在任务栏Windows 11上

将不受支持的时区UT重写为UTC是否节省?

在Spring Boot中使用哪个Java类来存储创建时间戳?

有没有办法让扩展变得多态?

测试何时使用Mockito强制转换对象会导致ClassCastException

如何从日志(log)行中删除包名称?

如何从HttpResponse实例获取Entity对象的内容?

将双倍转换为百分比

使IntelliJ在导入时优先 Select 一个类或将另一个标记为错误

接受类及其接口的Java类型(矛盾)

在Java中使用StorageReference将数据从Firebase存储添加到数组列表

Java 17与Java 8双重表示法

当使用不同的参数类型调用时,为什么围绕Objects.equals的类型安全包装不会失败?

Java System.getProperty在哪里检索user.home?

无泄漏函数的Java DRY

无法转换 [Ljava.lang.Object;将 Object[] 传递给 Object... 可变参数