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

73

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 begonnen habe.

raf
quelle
7
Dies kann einigen helfen: stackoverflow.com/questions/35842/process-id-in-java
Grammin
2
Keine Ahnung, wie es in Java gemacht wird, aber falls es Ihr Skript ist, können Sie es ändern, so dass es seine PID ausgibt und es von p.getInputStream () analysiert.
Maaartinus
5
KEIN "genaues Duplikat" für denjenigen, der für den Abschluss gestimmt hat. Diese Frage hat damit zu tun, die PID des Java-Prozesses selbst zu finden, nicht mit einem neuen Prozess, den der Java-Prozess hervorgebracht hat.
Mark Peters
Wofür benötigen Sie die PID überhaupt?
Raedwald
2
Es ist möglich, dass Java 9 eine Standard-API dafür bereitstellt
Raedwald

Antworten:

35

Da die Java 9- Klasse Processeine neue Methode hat long pid(), ist sie so einfach wie

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

Hierfür gibt es noch keine öffentliche API. siehe Sun Bug 4244896 , Sun Bug 4250622

Um dieses Problem zu umgehen:

Runtime.exec(...)

Gibt ein Objekt vom Typ zurück

java.lang.Process

Die Process-Klasse ist abstrakt, und Sie erhalten eine Unterklasse von Process zurück, die für Ihr Betriebssystem entwickelt wurde. Auf Macs wird beispielsweise java.lang.UnixProcessein privates Feld mit dem Namen "Private" zurückgegeben pid. Mit Reflection können Sie den Wert dieses Feldes leicht ermitteln. Dies ist zwar ein Hack, aber es könnte helfen. Wofür brauchst du das überhaupt PID?

Amir Afghani
quelle
11
Unter Windows wird java.lang.ProcessImpl zurückgegeben, das keine Vorstellung von PID hat. Ihre Lösung ist leider nicht plattformübergreifend.
Espinosa
1
Es gibt ein weiteres recht gutes Beispiel, das eine angemessene plattformübergreifende Unterstützung bietet: github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/…
satyagraha
1
Ich weiß nichts über das OP, aber ich muss in der Lage sein, den Prozess zu beenden und möglicherweise irgendwann neu zu starten.
Nate Lockwood
Beachten Sie, dass der Hack unter Java 9 unter Linux nicht mehr funktioniert - bei Mac nicht sicher.
Oliver Gondža
Für Java 9+ Benutzer ist hier eine JDK-Lösung: stackoverflow.com/a/42104000/363573
Stephan
24

Diese Seite hat das HOWTO:

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

Unter Windows:

Runtime.exec(..)

Gibt eine Instanz von "java.lang.Win32Process") ODER "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. Diese Seite enthält Details dazu.

Shamit Verma
quelle
Siehe Antwort von arcsin, wie es mit JNA geht.
Panayotis
Siehe Antworten von LRBH10 und czerny. Dies sind bessere Ansätze im Jahr 2019.
Shamit Verma
21

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;
  }
LRBH10
quelle
1
@idelvall was willst du hier threadsicher sein? Pid ist endgültig, das Lesen seines Wertes ist sicher.
Miha_x64
@ Miha_x64 Field Ich denke, ich habe gesagt, dass das Denken getDeclaredField()immer dieselbe Instanz zurückgibt (das tut es nicht). Also kann ich diese Aussage nicht mehr halten
idelvall
14

Fügen Sie jna (beide „JNA“ und „JNA Platform“) in Ihrer Bibliothek 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 .

arcsin
quelle
Könnten Sie bitte erklären, was der obige Code bewirkt? Auf diese Weise werden zukünftige Besucher verstehen. Vielen Dank
Drew Szurko
1
Die Antwort von Shamit Verma stellt fest, warum dies so gemacht werden muss. Windows gibt Ihnen keine PID, sondern ein Handle. Daher müssen Sie das Handle in PID umwandeln.
Panayotis
8

Ich glaube, ich habe eine Lösung gefunden, die bei der Arbeit 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 erzeugen / 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. Der Neue ist dein Typ.

Da Sie nur nach untergeordneten Prozessen suchen, können Sie von keinem anderen Prozess auf demselben Computer falsch behandelt werden. Mit JVM-weitem Mutex können Sie sicher sein, 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 und, was noch wichtiger ist, dies bereits in mehreren Bibliotheken durchgeführt wurde.

Im Folgenden finden Sie eine Implementierung der obigen Idee unter Verwendung der JavaSysMon- Bibliothek. 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;
        }
    }
}
Martin Modrák
quelle
4

In meinen Tests hatten alle IMPL-Klassen das Feld "pid". Das hat bei mir 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 einfach sicher, dass der zurückgegebene Wert nicht -1 ist. Wenn dies der Fall ist, analysieren Sie die Ausgabe von ps.

Jared Rummler
quelle
5
Es gilt nicht für java.lang.ProcessImpl. Es schlägt für mich zum Beispiel unter Windows fehl.
Oliver Gondža
2

Ich habe einen nicht portablen Ansatz verwendet, um die UNIX-PID von dem ProcessObjekt abzurufen, das sehr einfach zu befolgen ist.

SCHRITT 1: Verwenden Sie einige Reflection-API-Aufrufe, um die ProcessImplementierungsklasse auf der Zielserver-JRE zu identifizieren (denken Sie daran, dass dies Processeine abstrakte Klasse ist). Wenn Ihre UNIX-Implementierung meiner entspricht, wird eine Implementierungsklasse mit einer Eigenschaft namens pidangezeigt, die die PID des Prozesses enthält. Hier ist der Protokollierungscode, 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, Code, um die ProcessImplementierungsklasse zu stehlen und die PID mithilfe der Reflection-API daraus abzurufen. Der folgende Code funktioniert für mich in Bezug auf UNIX. Möglicherweise müssen Sie die Konstanten EXPECTED_IMPL_CLASS_NAMEund anpassen EXPECTED_PID_FIELD_NAME, 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
}
Jim Tough
quelle
1

Dies ist keine generische Antwort.

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

Zum Beispiel bietet LibreOffice Angebote an --pidfile={file}, siehe die Dokumente .

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

Ondra Žižka
quelle
Dies ist eine weitaus bessere Lösung als die Java / Linux-Lösung. Danke für die Idee!
Loganathan
0

Es gibt keine einfache Lösung. In der Vergangenheit habe ich einen anderen Prozess gestartet, um entweder den psBefehl auf Unix-ähnlichen Systemen oder den tasklistBefehl unter Windows auszuführen und dann die Ausgabe dieses Befehls auf die gewünschte PID zu analysieren. In Wirklichkeit habe ich diesen Code in ein separates Shell-Skript für jede Plattform eingefügt, die gerade die PID zurückgegeben hat, damit ich das 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 Problem.

Stewart Murrie
quelle
0

Ich glaube, der einzige tragbare Weg, dies zu tun, besteht darin, einen (untergeordneten) Prozess über einen anderen (übergeordneten) Java-Prozess auszuführen, der mich über die tatsächliche PID des übergeordneten Prozesses informiert. Der untergeordnete Prozess kann alles sein.

Der Code dieses Wrappers 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 eine JAR-Datei mit nur dieser und rufen Sie sie mit folgenden Befehlsargumenten 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...);
Panayotis
quelle
0

Wenn die Portabilität keine Rolle spielt und Sie die PID nur unter Windows ohne großen Aufwand abrufen möchten, während Sie Code verwenden, der getestet wurde und bekanntermaßen auf allen modernen Windows-Versionen funktioniert, können Sie die Winp- Bibliothek von kohsuke verwenden. Es ist auch in Maven Central für den einfachen Verbrauch erhältlich.

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

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

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

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

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

Luciano
quelle
0

Eine Lösung besteht darin, die eigenwilligen Tools zu verwenden, 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:

  • Anruf ps an, um die Prozesse aufzulisten,
  • Finden Sie Ihren, weil Sie den Befehl kennen, mit dem Sie ihn gestartet haben.
  • Wenn es mehrere Prozesse mit demselben Befehl gibt, können Sie:
    • Fügen Sie ein weiteres Dummy-Argument hinzu, um sie zu unterscheiden
    • Verlassen Sie sich auf die zunehmende PID (nicht wirklich sicher, nicht gleichzeitig)
    • Überprüfen Sie den Zeitpunkt der Prozesserstellung (kann zu grob sein, um wirklich zu unterscheiden, auch nicht gleichzeitig)
    • Fügen Sie eine bestimmte Umgebungsvariable hinzu und listen Sie sie psebenfalls auf.
Ondra Žižka
quelle
0

Für GNU / Linux- und MacOS-Systeme (oder allgemein UNIX-ähnliche Systeme) habe ich die folgende Methode verwendet, die einwandfrei 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;
}
Csonuryilmaz
quelle
0

Unter Verwendung von JNA Unterstützung alter und neuer JVM zum Abrufen der Prozess-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;
}
Raj
quelle