如何获得进程 PID 我刚刚开始在 Java 程序?

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

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

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

134912 次浏览

There is no public API for this yet. see Sun Bug 4244896, Sun Bug 4250622

As a workaround:

Runtime.exec(...)

returns an Object of type

java.lang.Process

The Process class is abstract, and what you get back is some subclass of Process which is designed for your operating system. For example on Macs, it returns java.lang.UnixProcess which has a private field called pid. Using Reflection you can easily get the value of this field. This is admittedly a hack, but it might help. What do you need the PID for anyway?

There isn't a simple solution. The way I've done it in the past is to start another process to run either the ps command on Unix-like systems, or the tasklist command on Windows, and then parse the output of that command for the PID I want. In reality, I ended up putting that code into a separate shell script for each platform which just returned the PID, so that I could keep the Java piece as platform independent as possible. This doesn't work well for short-lived tasks, but that wasn't an issue for me.

This page has the HOWTO:

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

On Windows:

Runtime.exec(..)

Returns an instance of "java.lang.Win32Process") OR "java.lang.ProcessImpl"

Both have a private field "handle".

This is an OS handle for the process. You will have to use this + Win32 API to query PID. That page has details on how to do that.

I think I have found out a solution, that looks quite bulletproof while working on most platforms. Here is the idea:

  1. Create a JVM-wide mutex that you acquire before spawning new process/killing a process
  2. Use platform-dependent code to acquire list of child processes + pids of your JVM process
  3. Spawn new process
  4. Acquire new list of child processes + pids and compare with the previous list. The one that is new is your guy.

Since you check only for child processes, you cannot be wronged by some other process in the same machine. JVM-wide mutex than allows you to be sure, that the new process is the correct one.

Reading child process list is simpler than getting PID from process objects, because it does not require WIN API calls on windows, and, more importantly, it has been done already in several libs.

Below is an implementation of the above idea using JavaSysMon library. It

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

I used a non-portable approach to retrieve the UNIX PID from the Process object that is very simple to follow.

STEP 1: Use some Reflection API calls to identify the Process implementation class on the target server JRE (remember that Process is an abstract class). If your UNIX implementation is like mine, you will see an implementation class that has a property named pid that contains the PID of the process. Here is the logging code that I used.

    //--------------------------------------------------------------------
// 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());
}
//--------------------------------------------------------------------

STEP 2: Based on the implementation class and field name that you obtained from the Reflection logging, write some code to pickpocket the Process implementation class and retrieve the PID from it using the Reflection API. The code below works for me on my flavour of UNIX. You may have to adjust the EXPECTED_IMPL_CLASS_NAME and EXPECTED_PID_FIELD_NAME constants to make it work for you.

/**
* 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
}

In my testing all IMPL classes had the field "pid". This has worked for me:

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

Just make sure the returned value is not -1. If it is, then parse the output of ps.

the jnr-process project provides this capability.

It is part of the java native runtime used by jruby and can be considered a prototype for a future java-FFI

In Unix System (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;
}

Since Java 9 class Process has new method long pid(), so it is as simple as

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

Include jna (both "JNA" and "JNA Platform") in your library and use this function:

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

You can also download JNA from here and JNA Platform from here.

I believe the only portable way to do this, is to run a (child) process through another (parent) Java process, which will inform me the actual PID of the parent process. The child process could be anything.

The code of this wrapper is

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

To use it, create a jar file with just this one, and call it with command arguments this:

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...);

If portability is not a concern, and you just want to get the pid on Windows without a lot of hassle while using code that is tested and known to work on all modern versions of Windows, you can use kohsuke's winp library. It is also available on Maven Central for easy consumption.

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

There is an open-source library that has such a function, and it has cross-platform implementations: https://github.com/OpenHFT/Java-Thread-Affinity

It may be overkill just to get the PID, but if you want other things like CPU and thread id, and specifically thread affinity, it may be adequate for you.

To get the current thread's PID, just call Affinity.getAffinityImpl().getProcessId().

This is implemented using JNA (see arcsin's answer).

This is not a generic answer.

However: Some programs, especially services and long-running programs, create (or offer to create, optionally) a "pid file".

For instance, LibreOffice offers --pidfile={file}, see the docs.

I was looking for quite some time for a Java/Linux solution but the PID was (in my case) lying at hand.

One solution is to use the idiosyncratic tools the platform offers:

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

Disclaimer: Not tested, but you get the idea:

  • Call ps to list the processes,
  • Find your one because you know the command you launched it with.
  • If there are multiple processes with the same command, you can:
    • Add another dummy argument to differentiate them
    • Rely on the increasing PID (not really safe, not concurrent)
    • Check the time of process creation (could be too coarse to really differentiate, also not concurrent)
    • Add a specific environment variable and list it with ps too.

For GNU/Linux & MacOS (or generally UNIX like) systems, I've used below method which works fine:

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

Using JNA, supporting old and new JVM to get process 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;
}

I made a quick and dirty solution for those who are still stuck with Java 8

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

I hope you find this snippet useful.