僵尸进程

  在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等.但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等),直到父进程通过wait / waitpid来取时才释放,此时该进程便成为僵尸进程。


1.什么是僵尸进程?
    UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,而父进程还没有结束,那么他将变成一个僵尸进程.
    如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程,因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init来接管他,成为他的父进程,从而保证每个进程都会有一个父进程.而Init进程会自动wait其子进程,因此被Init接管的所有进程都不会变成僵尸进程.

2.子进程结束后为什么要进入僵尸状态?
    因为父进程可能要取得子进程的退出状态等信息。

3.僵尸状态是每个子进程比经过的状态吗?
    是的。任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
    如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

4.如何查看僵尸进程:
    $ ps -el
    其中,有标记为Z的进程就是僵尸进程
    S代表休眠状态;D代表不可中断的休眠状态;R代表运行状态;Z代表僵死状态;T代表停止或跟踪状态

5.僵尸进程的避免
    1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起
    2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收
    3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号
    4. 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。

#include "apue.h"
#include <sys/wait.h>

int main(void)
{
    pid_t pid;
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    } 
    else if (pid == 0)
    { 
        /**//* first child */
        if ((pid = fork()) < 0)
            err_sys("fork error");
        else if (pid > 0)
            exit(0); 
        /**//* parent from second fork == first child */
        /**//*
        * We're the second child; our parent becomes init as soon
        * as our real parent calls exit() in the statement above.
        * Here's where we'd continue executing, knowing that when
        * we're done, init will reap our status.
        */
        sleep(2);
        printf("second child, parent pid = %d ", getppid());
        exit(0);
    }

    if (waitpid(pid, NULL, 0) != pid) /**//* wait for first child */
    err_sys("waitpid error");

    /**//*
    * We're the parent (the original process); we continue executing,
    * knowing that we're not the parent of the second child.
    */
    exit(0);
}

 

Netty 是一个高性能的网络编程框架,广泛用于构建服务器端和客户端应用程序。然而,在某些情况下,使用 Netty 可能会导致僵尸进程(Zombie Process)的出现。僵尸进程是指已经终止但其父进程尚未调用 `wait()` 或 `waitpid()` 来获取其终止状态的进程。这类进程仍然占用系统资源(如进程 ID),如果数量过多,可能会影响系统的稳定性和性能。 ### 僵尸进程的成因 在 Netty 中,僵尸进程的产生通常以下因素有关: - **子进程的创建**:Netty 本身并不直接创建子进程,但在某些场景下,比如外部命令交互、使用 `ProcessBuilder` 或 `Runtime.exec()` 启动子进程时,可能会导致僵尸进程的产生。如果父进程没有正确等待子进程的退出状态,子进程就会变成僵尸进程。 - **未处理的信号**:在 Unix/Linux 系统中,当子进程终止时,会向父进程发送 `SIGCHLD` 信号。如果父进程没有正确处理该信号,或者忽略了 `SIGCHLD` 信号的处理,子进程的状态将不会被回收,从而导致僵尸进程的产生 [^1]。 ### 僵尸进程的排查 要排查 Netty 应用中是否存在僵尸进程问题,可以通过以下几种方式进行: 1. **使用 `ps` 命令**:通过 `ps -ef | grep defunct` 可以查看当前系统中的僵尸进程僵尸进程的状态通常显示为 `<defunct>`。 2. **检查进程树**:可以使用 `pstree` 命令来查看进程树,确认哪些进程是僵尸进程,并追踪它们的父进程。 3. **监控系统资源**:使用 `top` 或 `htop` 等工具监控系统的进程状态,观察是否有大量僵尸进程存在。 ### 解决方案 为了防止 Netty 应用中出现僵尸进程,可以采取以下措施: 1. **确保子进程被正确回收**:在创建子进程时,确保父进程调用了 `wait()` 或 `waitpid()` 来回收子进程的状态。如果使用 Java 的 `ProcessBuilder` 或 `Runtime.exec()` 创建子进程,可以通过调用 `process.waitFor()` 来等待子进程的结束 [^1]。 ```java Process process = Runtime.getRuntime().exec("some command"); int exitCode = process.waitFor(); ``` 2. **忽略 `SIGCHLD` 信号**:在某些情况下,可以考虑忽略 `SIGCHLD` 信号,这样系统会自动回收子进程的状态,避免僵尸进程的产生。可以通过 `Signal` 类来忽略信号 [^1]。 ```java Signal.handle(new Signal("CHLD"), signal -> {}); ``` 3. **使用守护线程**:如果子进程是由某个线程启动的,可以将该线程设置为守护线程,这样即使子进程没有被正确回收,也不会导致僵尸进程的长期存在。 4. **使用第三方库**:考虑使用更高级别的库来管理子进程,例如 Apache Commons Exec,它提供了更强大的功能来处理子进程的生命周期 [^1]。 5. **定期清理僵尸进程**:在某些情况下,可以通过编写脚本或使用工具定期清理僵尸进程。例如,使用 `kill -9 <pid>` 来强制终止僵尸进程,但这并不是推荐的做法,因为这可能会导致资源泄漏。 ### 总结 虽然 Netty 本身不会直接导致僵尸进程的产生,但在外部命令交互或创建子进程时,如果不注意进程的生命周期管理,就可能出现僵尸进程问题。通过合理的设计和管理,可以有效避免此类问题的发生,确保系统的稳定性和性能。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值