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.
I think I have found out a solution, that looks quite bulletproof while working on most platforms.
Here is the idea:
Create a JVM-wide mutex that you acquire before spawning new process/killing a process
Use platform-dependent code to acquire list of child processes + pids of your JVM process
Spawn new process
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
}
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:
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();
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).