背景

在图片或者视频处理, 或者其他需求,需要调用其他工具做些操作或者处理(如调用shell,shell 里面在调用其他子进程)等, 用Java的Process来调用其他进程处理, 当使用process.destroyForcibly()时, 会有一些坑在, 父进程被kill, 子进程未被kill,导致存在过多僵死进程,造成机器过载, 影响服务。

实例分析

Process.destroyForcibly代码片段

//UNIXProcess
@Override
public Process destroyForcibly() {
    destroy(true);
    return this;
}

对应底层native代码

JNIEXPORT void JNICALL
Java_java_lang_UNIXProcess_destroyProcess(JNIEnv *env,
                                          jobject junk,
                                          jint pid,
                                          jboolean force)
{
    int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM;
    kill(pid, sig);
}

从底层C代码,可以看到, 当使用force时, 发送的中SIGKILL也就是-9强杀, 这个信号是不能被捕捉的(内核的安全机制原因)。SIGTERM也是终止进程的信号, 但可以被阻塞、处理和忽略。

解决

在process.destroyForcibly()之前做下cleanup工作。

cleanup的工作是拿到父进程, 再kill所有的子进程

//kill sub processes
private void cleanup(Process p) {
    int pid = ProcessUtils.getPid(p);
    if (pid != -1) {
        try {
            Process cleanUpProcess = new ProcessBuilder("/usr/bin/pkill", "-9", "-P", String.valueOf(pid)).start();
            int exitcode = cleanUpProcess.waitFor();
            ProcessLogger.warn("CLEANUP", String.format("pid: %d exitcode: %d", pid, exitcode));
        } catch (Exception e) {
            ProcessLogger.warn("CLEANUP pid: " + pid, e);
        }
    } else {
        ProcessLogger.warn("CLEANUP", "get pid error");
    }
 }
public static synchronized int getPid(Process p) {
    int pid = -1;
    try {
        if (p.getClass().getName().equals("java.lang.UNIXProcess")) {
            Field f = p.getClass().getDeclaredField("pid");
            f.setAccessible(true);
            pid = f.getInt(p);
            f.setAccessible(false);
        }
    } catch (Exception e) {
        pid = -1;
    }
    return pid;
}

**说明:**获取pid的方式, 在JDK9之前只能通过反射机制, 在JDK9可以直接通过接口拿到。

题外话

如果是直接通过脚本处理, 可以通过trap+pkill, 捕捉信息做清理工作

cleanup() {
        #do clean up
        >&2 echo "interrupted!"
        exit 2
}

trap cleanup SIGHUP SIGINT SIGQUIT SIGTERM