如何获取我刚刚在Java程序中开始的进程的PID?


73

我已经开始执行以下代码

 ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "path");
 try {
     Process p = pb.start();       
 } 
 catch (IOException ex) {}

现在,我需要知道我刚刚开始的进程的pid。



2
不知道如何在Java中执行此操作,但是如果是您的脚本,则可以对其进行修改,因此它将输出它的pid并从p.getInputStream()进行解析。
maaartinus

5
不是投票关闭的人的“精确重复”。这个问题与查找Java进程本身的pid有关,而不是与Java进程产生的新进程有关。
马克·彼得斯

无论如何,您需要什么PID?
Raedwald

2
Java 9可能会为此提供标准API:openjdk.java.net/jeps/102
Raedwald,

Answers:


36

由于Java 9Process具有新方法long pid(),因此它很简单

ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "path");
try {
    Process p = pb.start();
    long pid = p.pid();      
} catch (IOException ex) {
    // ...
}

27

尚无公共API。参见Sun Bug 4244896,Sun Bug 4250622

解决方法:

Runtime.exec(...)

返回一个类型的对象

java.lang.Process

Process类是抽象的,您得到的是为您的操作系统设计的Process的某些子类。例如,在Mac上,它会返回java.lang.UnixProcess一个名为的私有字段pid。使用反射,您可以轻松获得该字段的值。诚然,这是一种hack,但可能会有所帮助。PID无论如何,您需要什么?


11
在Windows上,它返回不带PID概念的java.lang.ProcessImpl。不幸的是,您的解决方案并非跨平台。
Espinosa 2013年

1
还有一个很好的例子,提供了合理的跨平台支持,网址
satyagraha

1
我不了解OP,但我需要能够终止并可能在某个时候重新启动该过程。
Nate Lockwood

请注意,该破解将不再在Linux上的Java 9上起作用-不确定Mac。
奥利弗·冈萨(OliverGondža)'17

对于Java 9+用户,这是仅JDK的解决方案:stackoverflow.com/a/42104000/363573
Stephan

24

此页面包含如何:

http://www.golesny.de/p/code/javagetpid

在Windows上:

Runtime.exec(..)

返回“ java.lang.Win32Process”的实例)或“ java.lang.ProcessImpl”

两者都有一个专用字段“句柄”。

这是该过程的操作系统句柄。您将必须使用此+ Win32 API来查询PID。该页面包含有关如何执行此操作的详细信息。


有关如何使用JNA的信息,请参阅arcsin的答案。
Panayotis

请参阅LRBH10和czerny的答案。这些是在2019
。– Shamit Verma,

21

在Unix系统(Linux和Mac)中

 public static synchronized long getPidOfProcess(Process p) {
    long pid = -1;

    try {
      if (p.getClass().getName().equals("java.lang.UNIXProcess")) {
        Field f = p.getClass().getDeclaredField("pid");
        f.setAccessible(true);
        pid = f.getLong(p);
        f.setAccessible(false);
      }
    } catch (Exception e) {
      pid = -1;
    }
    return pid;
  }

1
@idelvall您想在这里实现线程安全吗?出价是最终的,读取其值是安全的。
Miha_x64 '18

@ Miha_x64字段我想我曾说过,这种想法getDeclaredField()总是返回相同的实例(不是)。因此,我再也无法保留该声明了
-idelvall

14

在您的库中包含jna(“ JNA”和“ JNA Platform”),并使用以下功能:

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinNT;
import java.lang.reflect.Field;

public static long getProcessID(Process p)
    {
        long result = -1;
        try
        {
            //for windows
            if (p.getClass().getName().equals("java.lang.Win32Process") ||
                   p.getClass().getName().equals("java.lang.ProcessImpl")) 
            {
                Field f = p.getClass().getDeclaredField("handle");
                f.setAccessible(true);              
                long handl = f.getLong(p);
                Kernel32 kernel = Kernel32.INSTANCE;
                WinNT.HANDLE hand = new WinNT.HANDLE();
                hand.setPointer(Pointer.createConstant(handl));
                result = kernel.GetProcessId(hand);
                f.setAccessible(false);
            }
            //for unix based operating systems
            else if (p.getClass().getName().equals("java.lang.UNIXProcess")) 
            {
                Field f = p.getClass().getDeclaredField("pid");
                f.setAccessible(true);
                result = f.getLong(p);
                f.setAccessible(false);
            }
        }
        catch(Exception ex)
        {
            result = -1;
        }
        return result;
    }

您也可以从这里下载JNA这里和JNA的平台,在这里


您能否解释一下以上代码的作用?这样,任何未来的访客都将理解。谢谢
Drew Szurko '17

1
Shamit Verma的答案指出了为什么需要采用这种方式。Windows没有给您PID,而是一个句柄,因此您需要将句柄转换为PID。
Panayotis

8

我想我已经找到了一个解决方案,在大多数平台上工作时看起来都非常安全。这是想法:

  1. 创建在生成新进程/杀死进程之前获取的JVM范围的互斥锁
  2. 使用平台相关的代码来获取子进程列表和JVM进程的pid
  3. 产生新流程
  4. 获取子进程+ pid的新列表,并与前一个列表进行比较。新手是您的家伙。

由于只检查子进程,因此不会被同一台计算机上的其他进程所困扰。JVM范围的互斥锁可以使您确定,新进程是正确的。

读取子进程列表比从进程对象获取PID更简单,因为它不需要Windows上的WIN API调用,而且更重要的是,它已经在多个库中完成了。

下面是使用JavaSysMon库实现上述想法的方法。它

class UDKSpawner {

    private int uccPid;
    private Logger uccLog;

    /**
     * Mutex that forces only one child process to be spawned at a time. 
     * 
     */
    private static final Object spawnProcessMutex = new Object();

    /**
     * Spawns a new UDK process and sets {@link #uccPid} to it's PID. To work correctly,
     * the code relies on the fact that no other method in this JVM runs UDK processes and
     * that no method kills a process unless it acquires lock on spawnProcessMutex.
     * @param procBuilder
     * @return 
     */
    private Process spawnUDK(ProcessBuilder procBuilder) throws IOException {
        synchronized (spawnProcessMutex){            
            JavaSysMon monitor = new JavaSysMon();
            DirectUDKChildProcessVisitor beforeVisitor = new DirectUDKChildProcessVisitor();
            monitor.visitProcessTree(monitor.currentPid(), beforeVisitor);
            Set<Integer> alreadySpawnedProcesses = beforeVisitor.getUdkPids();

            Process proc = procBuilder.start();

            DirectUDKChildProcessVisitor afterVisitor = new DirectUDKChildProcessVisitor();
            monitor.visitProcessTree(monitor.currentPid(), afterVisitor);
            Set<Integer> newProcesses = afterVisitor.getUdkPids();

            newProcesses.removeAll(alreadySpawnedProcesses);

            if(newProcesses.isEmpty()){
                uccLog.severe("There is no new UKD PID.");
            }
            else if(newProcesses.size() > 1){
                uccLog.severe("Multiple new candidate UDK PIDs");
            } else {
                uccPid = newProcesses.iterator().next();
            }
            return proc;
        }
    }    

    private void killUDKByPID(){
        if(uccPid < 0){
            uccLog.severe("Cannot kill UCC by PID. PID not set.");
            return;
        }
        synchronized(spawnProcessMutex){
            JavaSysMon monitor = new JavaSysMon();
            monitor.killProcessTree(uccPid, false);
        }
    }

    private static class DirectUDKChildProcessVisitor implements ProcessVisitor {
        Set<Integer> udkPids = new HashSet<Integer>();

        @Override
        public boolean visit(OsProcess op, int i) {
            if(op.processInfo().getName().equals("UDK.exe")){
                udkPids.add(op.processInfo().getPid());
            }
            return false;
        }

        public Set<Integer> getUdkPids() {
            return udkPids;
        }
    }
}

4

在我的测试中,所有IMPL类都具有“ pid”字段。这对我有用:

public static int getPid(Process process) {
    try {
        Class<?> cProcessImpl = process.getClass();
        Field fPid = cProcessImpl.getDeclaredField("pid");
        if (!fPid.isAccessible()) {
            fPid.setAccessible(true);
        }
        return fPid.getInt(process);
    } catch (Exception e) {
        return -1;
    }
}

只要确保返回的值不为-1。如果是,则解析的输出ps


6
它不适用于java.lang.ProcessImpl。例如,在Windows上对我来说失败。
奥利弗·冈萨(OliverGondža)

2

我使用了一种非便携式方法来从Process对象中检索UNIX PID,这很容易遵循。

步骤1: 使用一些Reflection API调用来标识Process目标服务器JRE上的实现类(请记住这Process是一个抽象类)。如果您的UNIX实现类似于我的UNIX实现,您将看到一个实现类,该类具有一个名为pidname的属性,其中包含该进程的PID。这是我使用的日志记录代码。

    //--------------------------------------------------------------------
    // Jim Tough - 2014-11-04
    // This temporary Reflection code is used to log the name of the
    // class that implements the abstract Process class on the target
    // JRE, all of its 'Fields' (properties and methods) and the value
    // of each field.
    //
    // I only care about how this behaves on our UNIX servers, so I'll
    // deploy a snapshot release of this code to a QA server, run it once,
    // then check the logs.
    //
    // TODO Remove this logging code before building final release!
    final Class<?> clazz = process.getClass();
    logger.info("Concrete implementation of " + Process.class.getName() +
            " is: " + clazz.getName());
    // Array of all fields in this class, regardless of access level
    final Field[] allFields = clazz.getDeclaredFields();
    for (Field field : allFields) {
        field.setAccessible(true); // allows access to non-public fields
        Class<?> fieldClass = field.getType();
        StringBuilder sb = new StringBuilder(field.getName());
        sb.append(" | type: ");
        sb.append(fieldClass.getName());
        sb.append(" | value: [");
        Object fieldValue = null;
        try {
            fieldValue = field.get(process);
            sb.append(fieldValue);
            sb.append("]");
        } catch (Exception e) {
            logger.error("Unable to get value for [" +
                    field.getName() + "]", e);
        }
        logger.info(sb.toString());
    }
    //--------------------------------------------------------------------

步骤2: 根据您从Reflection日志记录中获得的实现类和字段名称,编写一些代码来窃取Process实现类,并使用Reflection API从中检索PID。下面的代码可根据我的UNIX风格为我工作。您可能需要调整EXPECTED_IMPL_CLASS_NAMEEXPECTED_PID_FIELD_NAME常数才能使其适合您。

/**
 * Get the process id (PID) associated with a {@code Process}
 * @param process {@code Process}, or null
 * @return Integer containing the PID of the process; null if the
 *  PID could not be retrieved or if a null parameter was supplied
 */
Integer retrievePID(final Process process) {
    if (process == null) {
        return null;
    }

    //--------------------------------------------------------------------
    // Jim Tough - 2014-11-04
    // NON PORTABLE CODE WARNING!
    // The code in this block works on the company UNIX servers, but may
    // not work on *any* UNIX server. Definitely will not work on any
    // Windows Server instances.
    final String EXPECTED_IMPL_CLASS_NAME = "java.lang.UNIXProcess";
    final String EXPECTED_PID_FIELD_NAME = "pid";
    final Class<? extends Process> processImplClass = process.getClass();
    if (processImplClass.getName().equals(EXPECTED_IMPL_CLASS_NAME)) {
        try {
            Field f = processImplClass.getDeclaredField(
                    EXPECTED_PID_FIELD_NAME);
            f.setAccessible(true); // allows access to non-public fields
            int pid = f.getInt(process);
            return pid;
        } catch (Exception e) {
            logger.warn("Unable to get PID", e);
        }
    } else {
        logger.warn(Process.class.getName() + " implementation was not " +
                EXPECTED_IMPL_CLASS_NAME + " - cannot retrieve PID" +
                " | actual type was: " + processImplClass.getName());
    }
    //--------------------------------------------------------------------

    return null; // If PID was not retrievable, just return null
}

1

这不是一个通用的答案。

但是:有些程序,尤其是服务和长期运行的程序,会创建(或提供创建(可选))“ pid文件”的信息。

例如,LibreOffice提供--pidfile={file},请参阅文档

我一直在寻找Java / Linux解决方案一些时间,但是PID(以我为例)就在眼前。


这是比Java / Linux解决方案更好的解决方案。谢谢你的主意!
Loganathan

0

没有一个简单的解决方案。我过去做的方法是启动另一个进程,以ps在类似Unix的系统上运行该命令,或者tasklist在Windows上运行该命令,然后为我想要的PID解析该命令的输出。实际上,我最终将代码放入刚刚返回PID的每个平台的单独的Shell脚本中,以便可以使Java代码尽可能地独立于平台。这对于短暂的任务来说效果不佳,但这对我来说不是问题。


0

JNR处理项目提供了这种能力。

它是jruby使用的Java本机运行时的一部分,可以视为将来的Java-FFI的原型。


0

我相信唯一可移植的方法是通过另一个(父)Java进程运行一个(子)进程,这将通知我父进程的实际PID。子进程可以是任何东西。

该包装器的代码是

package com.panayotis.wrapper;

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;

public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        System.out.println(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
        ProcessBuilder pb = new ProcessBuilder(args);
        pb.directory(new File(System.getProperty("user.dir")));
        pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.start().waitFor();
    }
}

要使用它,只需创建一个jar文件,并使用以下命令参数调用它:

String java = System.getProperty("java.home") + separator + "bin" + separator + "java.exe";
String jar_wrapper = "path\\of\\wrapper.jar";

String[] args = new String[]{java, "-cp", jar_wrapper, "com.panayotis.wrapper.Main", actual_exec_args...);

0

如果不考虑可移植性,并且您只想在Windows上轻松使用pid,同时使用经过测试且已知可在所有现代Windows版本上运行的代码,则可以使用kohsuke的winp库。它也可以在Maven Central中使用,以方便使用。

Process process = //...;
WinProcess wp = new WinProcess(process);
int pid = wp.getPid();

0

有一个具有这种功能的开源库,它具有跨平台的实现:https : //github.com/OpenHFT/Java-Thread-Affinity

仅获取PID可能会过大,但是如果您想要其他东西,例如CPU和线程ID,特别是线程亲和力,它可能就足够了。

要获取当前线程的PID,只需调用Affinity.getAffinityImpl().getProcessId()

这是使用JNA实现的(请参阅arcsin的答案)。


0

一种解决方案是使用平台提供的特质工具:

private static String invokeLinuxPsProcess(String filterByCommand) {
    List<String> args = Arrays.asList("ps -e -o stat,pid,unit,args=".split(" +"));
    // Example output:
    // Sl   22245 bpds-api.service                /opt/libreoffice5.4/program/soffice.bin --headless
    // Z    22250 -                               [soffice.bin] <defunct>

    try {
        Process psAux = new ProcessBuilder(args).redirectErrorStream(true).start();
        try {
            Thread.sleep(100); // TODO: Find some passive way.
        } catch (InterruptedException e) { }

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(psAux.getInputStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.contains(filterByCommand))
                    continue;
                String[] parts = line.split("\\w+");
                if (parts.length < 4)
                    throw new RuntimeException("Unexpected format of the `ps` line, expected at least 4 columns:\n\t" + line);
                String pid = parts[1];
                return pid;
            }
        }
    }
    catch (IOException ex) {
        log.warn(String.format("Failed executing %s: %s", args, ex.getMessage()), ex);
    }
    return null;
}

免责声明:未经测试,但您知道了:

  • 致电ps列出流程,
  • 找到您的一个,因为您知道启动它的命令。
  • 如果有多个使用同一命令的进程,则可以:
    • 添加另一个虚拟参数以区分它们
    • 依靠增加的PID(不是很安全,不是并发的)
    • 检查流程创建的时间(可能太粗糙而无法真正区分,也不能并发)
    • 添加一个特定的环境变量并将其列出ps

0

对于GNU / Linux和MacOS(或通常的UNIX之类的)系统,我使用了下面的方法,该方法效果很好:

private int tryGetPid(Process process)
{
    if (process.getClass().getName().equals("java.lang.UNIXProcess"))
    {
        try
        {
            Field f = process.getClass().getDeclaredField("pid");
            f.setAccessible(true);
            return f.getInt(process);
        }
        catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException | SecurityException e)
        {
        }
    }

    return 0;
}

0

使用JNA,支持新旧JVM来获取进程ID

public static long getProcessId(Process p){
    long pid = -1;
    try {
      pid = p.pid();
    } catch (NoSuchMethodError e) {
        try
        {
            //for windows
            if (p.getClass().getName().equals("java.lang.Win32Process") || p.getClass().getName().equals("java.lang.ProcessImpl")) {
                Field f = p.getClass().getDeclaredField("handle");
                f.setAccessible(true);              
                long handl = f.getLong(p);
                Kernel32 kernel = Kernel32.INSTANCE;
                WinNT.HANDLE hand = new WinNT.HANDLE();
                hand.setPointer(Pointer.createConstant(handl));
                pid = kernel.GetProcessId(hand);
                f.setAccessible(false);
            }
            //for unix based operating systems
            else if (p.getClass().getName().equals("java.lang.UNIXProcess")) 
            {
                Field f = p.getClass().getDeclaredField("pid");
                f.setAccessible(true);
                pid = f.getLong(p);
                f.setAccessible(false);
            }
        }
        catch(Exception ex)
        {
            pid = -1;
        }
    }        
    return pid;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.