【问题标题】:How to get PID of process I've just started within java program?如何获取我刚刚在 java 程序中启动的进程的 PID?
【发布时间】:2011-06-12 15:16:45
【问题描述】:

我已经使用以下代码启动了一个进程

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

现在我需要知道我刚刚启动的进程的 pid。

【问题讨论】:

  • 不知道如何在 Java 中做到这一点,但如果是你的脚本,你可以修改它,所以它输出它的 pid 并从 p.getInputStream() 解析它。
  • 对投票关闭的人来说不是“完全重复”。这个问题与查找 Java 进程本身的 pid 有关,而不是 Java 进程产生的新进程。
  • 这将返回 running 主机 VM 的 PID,最初的问题是询问从正在运行的 Java 应用程序启动的子进程的 PID 是什么。这个问题解决了它:stackoverflow.com/questions/5284139/…
  • Java 9 可能会为此提供标准 API:openjdk.java.net/jeps/102

标签: java process pid processbuilder


【解决方案1】:

由于Java 9Process有新方法long pid(),所以就这么简单

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

【讨论】:

    【解决方案2】:

    目前还没有公开的 API。见孙Bug 4244896,孙Bug 4250622

    作为一种解决方法:

    Runtime.exec(...)
    

    返回一个对象类型

    java.lang.Process
    

    Process 类是抽象的,您得到的是为您的操作系统设计的 Process 的某个子类。例如,在 Mac 上,它返回 java.lang.UnixProcess,其中有一个名为 pid 的私有字段。使用反射你可以很容易地得到这个字段的值。这无疑是一种黑客行为,但它可能会有所帮助。无论如何,您需要PID 做什么?

    【讨论】:

    • 在 Windows 上,它返回没有 PID 概念的 java.lang.ProcessImpl。不幸的是,您的解决方案不是跨平台的。
    • 还有一个很好的例子,它提供了合理的跨平台支持:github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/…
    • 我不了解 OP,但我需要能够在某个时候终止并可能重新启动该进程。
    • 请注意,该 hack 将不再适用于 Linux 上的 Java 9 - Mac 不确定。
    • 对于 Java 9+ 用户,这里是 JDK 唯一的解决方案:stackoverflow.com/a/42104000/363573
    【解决方案3】:

    这个页面有HOWTO:

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

    在 Windows 上:

    Runtime.exec(..)
    

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

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

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

    【讨论】:

    • 查看 arcsin 的回答,了解如何使用 JNA 进行操作。
    • 参考 LRBH10 和 czerny 的回答。这些是 2019 年更好的方法。
    【解决方案4】:

    在 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;
      }
    

    【讨论】:

    • @idelvall 你想在这里实现什么线程安全? Pid 是最终的,读取它的值是安全的。
    • @Miha_x64 Field 我想我说过认为getDeclaredField() 总是返回相同的实例(它没有)。所以我不能再坚持这种说法了
    【解决方案5】:

    在您的库中包含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;
        }
    

    您也可以从here 下载JNA,从here 下载JNA Platform。

    【讨论】:

    • 你能解释一下上面的代码是做什么的吗?这样任何未来的访问者都会理解。谢谢
    • Shamit Verma 的回答说明了为什么需要这样做。 Windows 不会给你一个 PID 而是一个句柄,所以你需要将句柄转换为 PID。
    【解决方案6】:

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

    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;
            }
        }
    }
    

    【讨论】:

      【解决方案7】:

      在我的测试中,所有 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的输出。

      【讨论】:

      • 不适用于java.lang.ProcessImpl。例如,它在 Windows 上对我来说失败了。
      【解决方案8】:

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

      第 1 步: 使用一些反射 API 调用来识别目标服务器 JRE 上的 Process 实现类(请记住,Process 是一个抽象类)。如果您的 UNIX 实现与我的类似,您将看到一个实现类,它有一个名为 pid 的属性,其中包含进程的 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 步: 根据您从反射日志中获得的实现类和字段名称,编写一些代码来扒窃Process 实现类并使用反射 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
      }
      

      【讨论】:

        【解决方案9】:

        这不是一个通用的答案。

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

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

        我一直在寻找 Java/Linux 解决方案,但 PID(在我的情况下)就在手边。

        【讨论】:

        • 这是比 Java/Linux 解决方案好得多的解决方案。感谢您的想法!
        【解决方案10】:

        没有简单的解决方案。我过去的做法是启动另一个进程以在类 Unix 系统上运行 ps 命令或在 Windows 上运行 tasklist 命令,然后解析该命令的输出以获取 PID I想。实际上,我最终将该代码放入每个平台的单独 shell 脚本中,该脚本只返回 PID,这样我就可以使 Java 部分尽可能独立于平台。这不适用于短期任务,但这对我来说不是问题。

        【讨论】:

          【解决方案11】:

          jnr-process 项目提供此功能。

          它是 jruby 使用的 java 本机运行时的一部分,可以被认为是未来的原型java-FFI

          【讨论】:

            【解决方案12】:

            我相信唯一可移植的方法是通过另一个(父)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...);
            

            【讨论】:

              【解决方案13】:

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

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

              【讨论】:

                【解决方案14】:

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

                仅获取 PID 可能有点过头了,但如果您需要其他信息,例如 CPU 和线程 ID,特别是线程亲和性,这对您来说可能就足够了。

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

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

                【讨论】:

                  【解决方案15】:

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

                  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 列出它。

                  【讨论】:

                    【解决方案16】:

                    对于 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;
                    }
                    

                    【讨论】:

                      【解决方案17】:

                      使用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;
                      }
                      

                      【讨论】:

                        【解决方案18】:

                        我为那些仍然坚持使用 Java 8 的人做了一个快速而肮脏的解决方案

                        public long showID(Process process) {
                            return Long.parseLong(process.toString().split(", ")[0].replace("Process[pid=", ""));
                        }
                        

                        我希望你觉得这个 sn-p 有用。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2014-11-17
                          • 2015-05-05
                          • 2016-05-11
                          • 2011-04-09
                          • 2023-03-08
                          相关资源
                          最近更新 更多