it-swarm.com.de

Wie bekomme ich die PID des Prozesses, den ich gerade im Java-Programm gestartet habe?

Ich habe einen Prozess mit folgendem Code gestartet

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

Jetzt muss ich die PID des Prozesses kennen, die ich gerade angefangen habe.

63
raf

Es gibt noch keine öffentliche API dafür. siehe Sun Bug 4244896 , Sun Bug 4250622

Als Workaround: 

Runtime.exec(...)

gibt ein Objekt vom Typ zurück 

Java.lang.Process

Die Process-Klasse ist abstrakt, und Sie erhalten eine Unterklasse von Process, die für Ihr Betriebssystem entwickelt wurde. Auf Macs gibt es beispielsweise Java.lang.UnixProcess zurück, das ein privates Feld mit dem Namen pid enthält. Mit Reflection können Sie leicht den Wert dieses Feldes ermitteln. Dies ist zwar ein Hack, aber es könnte helfen. Wofür brauchen Sie PID?

24
Amir Afghani

Diese Seite hat das HOWTO:

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

Unter Windows:

Runtime.exec(..)

Gibt eine Instanz von "Java.lang.Win32Process") OR "Java.lang.ProcessImpl" zurück.

Beide haben ein privates Feld "Handle".

Dies ist ein Betriebssystem-Handle für den Prozess. Sie müssen diese + Win32-API verwenden, um die PID abzufragen. Auf dieser Seite finden Sie Einzelheiten dazu.

24
Shamit Verma

Im 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;
  }
19
LRBH10

Da Java 9 Klasse Process die neue Methode long pid() hat, ist dies so einfach wie

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

Fügen Sie jna (sowohl "JNA" als auch "JNA Platform") in Ihre Bibliothek ein und verwenden Sie diese Funktion:

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

Sie können JNA auch von hier und JNA Platform von hier herunterladen.

11
arcsin

Ich glaube, ich habe eine Lösung gefunden, die auf den meisten Plattformen ziemlich kugelsicher aussieht ... Hier ist die Idee:

  1. Erstellen Sie einen JVM-weiten Mutex, den Sie erwerben, bevor Sie einen neuen Prozess starten oder einen Prozess beenden
  2. Verwenden Sie plattformabhängigen Code, um eine Liste der untergeordneten Prozesse + Pids Ihres JVM-Prozesses abzurufen
  3. Neuen Prozess erzeugen
  4. Erwerben Sie eine neue Liste der untergeordneten Prozesse + Pids und vergleichen Sie sie mit der vorherigen Liste. Das Neue ist dein Kerl.

Da Sie nur nach untergeordneten Prozessen suchen, können Sie nicht durch einen anderen Prozess auf demselben Computer verfälscht werden. Ein JVM-breiter Mutex gibt Ihnen die Gewissheit, dass der neue Prozess der richtige ist.

Das Lesen der untergeordneten Prozessliste ist einfacher als das Abrufen der PID von Prozessobjekten, da für Windows keine WIN-API-Aufrufe erforderlich sind.

Nachfolgend finden Sie eine Implementierung der obigen Idee unter Verwendung von JavaSysMon library. Es

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;
        }
    }
}
8
Martin Modrák

In meinem Test hatten alle IMPL-Klassen das Feld "pid". Das hat für mich funktioniert:

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

Stellen Sie nur sicher, dass der zurückgegebene Wert nicht -1 ist. Wenn ja, dann parsen Sie die Ausgabe von ps.

3
Jared Rummler

Ich habe einen nicht portablen Ansatz verwendet, um die UNIX-PID aus dem Process-Objekt abzurufen, das sehr einfach zu verfolgen ist.

SCHRITT 1: Verwenden Sie einige Reflection-API-Aufrufe, um die Implementierungsklasse Process auf der JRE des Zielservers zu identifizieren (denken Sie daran, dass Process eine abstrakte Klasse ist). Wenn Ihre UNIX-Implementierung wie die meine ist, sehen Sie eine Implementierungsklasse mit einer Eigenschaft namens pid, die die PID des Prozesses enthält. Hier ist der Protokollcode, den ich verwendet habe.

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

SCHRITT 2: Schreiben Sie basierend auf der Implementierungsklasse und dem Feldnamen, die Sie aus der Reflection-Protokollierung erhalten haben, etwas Code, um die Process-Implementierungsklasse abzufangen und die PID mithilfe der Reflection-API abzurufen. Der folgende Code funktioniert für mich mit meinem Geschmack von UNIX. Möglicherweise müssen Sie die Konstanten EXPECTED_IMPL_CLASS_NAME und EXPECTED_PID_FIELD_NAME anpassen, damit es für Sie funktioniert.

/**
 * 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
}
2
Jim Tough

Dies ist keine generische Antwort.

Allerdings: Einige Programme, insbesondere Dienste und Programme mit langer Laufzeit, erstellen (oder bieten optional die Erstellung einer "PID-Datei") an.

Zum Beispiel bietet LibreOffice --pidfile={file} an, siehe docs .

Ich habe lange nach einer Java/Linux-Lösung gesucht, aber die PID lag (in meinem Fall) nahe.

1
Ondra Žižka

Es gibt keine einfache Lösung. Ich habe es in der Vergangenheit so gemacht, dass Sie einen anderen Prozess starten, um entweder den Befehl ps auf Unix-ähnlichen Systemen oder den Befehl tasklist unter Windows auszuführen, und dann die Ausgabe dieses Befehls für die von mir gewünschte PID analysieren. In Wirklichkeit habe ich diesen Code in ein separates Shell-Skript für jede Plattform eingefügt, das gerade die PID zurückgegeben hat, damit ich den Java-Teil so plattformunabhängig wie möglich halten kann. Dies funktioniert nicht gut für kurzlebige Aufgaben, aber das war für mich kein Thema. 

0
Stewart Murrie

Für GNU/Linux- und MacOS-Systeme (oder allgemein UNIX-ähnliche) Systeme habe ich die folgende Methode verwendet, die gut funktioniert:

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
csonuryilmaz

Ich glaube, der einzige tragbare Weg, dies zu tun, besteht darin, einen (untergeordneten) Prozess durch einen anderen (übergeordneten) Java-Prozess auszuführen, der mir die tatsächliche PID des übergeordneten Prozesses anzeigt. Der Kindprozess könnte alles sein.

Der Code für diesen Wrapper lautet

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

Um es zu verwenden, erstellen Sie einfach eine JAR-Datei mit dieser und rufen Sie sie mit Befehlsargumenten wie folgt auf:

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
Panayotis

das jnr-process project bietet diese Fähigkeit.

Es ist Teil der Java-Laufzeitumgebung, die von jruby verwendet wird, und kann als Prototyp für eine Zukunft betrachtet werden. Java-FFI

0
the8472

Eine Lösung ist die Verwendung der idiosynkratischen Tools, die die Plattform bietet:

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

Haftungsausschluss: Nicht getestet, aber Sie bekommen die Idee:

  • Rufen Sie ps auf, um die Prozesse aufzulisten.
  • Finden Sie Ihren, weil Sie wissen, mit welchem ​​Befehl Sie ihn gestartet haben.
  • Wenn mehrere Prozesse mit demselben Befehl vorhanden sind, können Sie:
    • Fügen Sie ein weiteres Dummy-Argument hinzu, um sie zu unterscheiden
    • Verlassen Sie sich auf die steigende PID (nicht wirklich sicher, nicht gleichzeitig)
    • Überprüfen Sie den Zeitpunkt der Prozesserstellung (kann zu grob sein, um wirklich unterschieden zu werden, auch nicht gleichzeitig)
    • Fügen Sie eine bestimmte Umgebungsvariable hinzu und listen Sie diese auch mit ps auf.
0
Ondra Žižka

Wenn die Portabilität kein Problem darstellt und Sie die PID unter Windows nur mühelos verwenden möchten, während Sie Code verwenden, der getestet wurde und für alle modernen Windows-Versionen bekannt ist, können Sie die kohsuke winp library verwenden. Es ist auch auf Maven Central für den einfachen Verbrauch erhältlich.

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

Es gibt eine Open-Source-Bibliothek mit einer solchen Funktion und plattformübergreifende Implementierungen: https://github.com/OpenHFT/Java-Thread-Affinity

Es kann übertrieben sein, nur um die PID zu erhalten, aber wenn Sie andere Dinge wie CPU- und Thread-ID und insbesondere die Thread-Affinität wünschen, kann dies für Sie ausreichend sein.

Um die PID des aktuellen Threads zu erhalten, rufen Sie einfach Affinity.getAffinityImpl().getProcessId() auf.

Dies wird mithilfe von JNA implementiert (siehe Antwort von arcsin).

0
Luciano