web-dev-qa-db-fra.com

Comment obtenir le PID du processus que je viens de commencer dans le programme Java?

J'ai commencé un processus avec le code suivant

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

Maintenant, j'ai besoin de connaître le processus que je viens de commencer.

63
raf

Il n'y a pas encore d'API publique pour cela. voir Sun Bug 4244896 , Sun Bug 4250622

Pour contourner le problème: 

Runtime.exec(...)

retourne un objet de type 

Java.lang.Process

La classe Process est abstraite et vous obtenez une sous-classe de Process conçue pour votre système d'exploitation. Par exemple, sur Mac, il retourne Java.lang.UnixProcess qui contient un champ privé appelé pid. En utilisant Reflection, vous pouvez facilement obtenir la valeur de ce champ. Ceci est certes un bidouillage, mais cela pourrait aider. De quoi avez-vous besoin de la PID de toute façon?

24
Amir Afghani

Cette page contient le HOWTO:

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

Sous Windows:

Runtime.exec(..)

Renvoie une instance de "Java.lang.Win32Process") OR "Java.lang.ProcessImpl"

Les deux ont un champ privé "handle".

C'est un descripteur de système d'exploitation pour le processus. Vous devrez utiliser cette API Win32 pour interroger le PID. Cette page a des détails sur la façon de le faire.

24
Shamit Verma

En système Unix (Linux et 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

Puisque Java 9, la classe Process a une nouvelle méthode long pid() , elle est donc aussi simple

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

Incluez jna ("JNA" et "JNA Platform") dans votre bibliothèque et utilisez cette fonction:

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

Vous pouvez également télécharger JNA à partir de ici et JNA Platform à partir de ici .

11
arcsin

Je pense avoir trouvé une solution qui semble tout à fait à l’abri des balles lorsqu’on travaille sur la plupart des plateformes. Voici l’idée:

  1. Créez un mutex à l'échelle de la machine virtuelle que vous acquérez avant de créer un nouveau processus/de tuer un processus
  2. Utilisez le code dépendant de la plate-forme pour obtenir la liste des processus enfants + pids de votre processus JVM
  3. Spawn nouveau processus
  4. Acquérir une nouvelle liste de processus enfants + pids et comparer avec la liste précédente. Celui qui est nouveau est votre gars.

Puisque vous ne vérifiez que les processus enfants, aucun autre processus de la même machine ne peut vous nuire. Un mutex à l'échelle de la machine virtuelle Java vous permet de vous assurer que le nouveau processus est le bon.

La lecture d'une liste de processus enfant est plus simple que d'obtenir le PID à partir d'objets de processus, car elle ne nécessite pas d'appels WIN API sur des fenêtres et, plus important encore, cela a déjà été fait dans plusieurs bibliothèques.

Vous trouverez ci-dessous une implémentation de l'idée ci-dessus à l'aide de JavaSysMon library. Il

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

Lors de mes tests, toutes les classes IMPL avaient le champ "pid". Cela a fonctionné pour moi:

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

Assurez-vous simplement que la valeur renvoyée n'est pas -1. Si tel est le cas, analysez la sortie de ps.

3
Jared Rummler

J'ai utilisé une approche non portable pour extraire le PID UNIX de l'objet Process qui est très simple à suivre.

ÉTAPE 1: .__ Utilisez des appels de l’API de Reflection pour identifier la classe d’implémentation Process sur le JRE du serveur cible (rappelez-vous que Process est une classe abstraite). Si votre implémentation UNIX ressemble à la mienne, vous verrez une classe d'implémentation qui possède une propriété nommée pid qui contient le PID du processus. Voici le code de connexion que j'ai utilisé.

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

ÉTAPE 2: .__ En fonction de la classe d'implémentation et du nom de champ obtenus à partir de la journalisation de Reflection, écrivez du code pour capturer la classe d'implémentation Process et récupérez le PID à l'aide de l'API de Reflection. Le code ci-dessous fonctionne pour moi sur ma version d'UNIX. Vous devrez peut-être ajuster les constantes EXPECTED_IMPL_CLASS_NAME et EXPECTED_PID_FIELD_NAME pour que cela fonctionne pour vous.

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

Ce n'est pas une réponse générique.

Cependant: Certains programmes, notamment les services et les programmes de longue durée, créent (ou proposent de créer, éventuellement) un "fichier pid".

Par exemple, LibreOffice offre --pidfile={file}, voir docs .

Je cherchais depuis quelque temps une solution Java/Linux, mais le PID était (dans mon cas) sous la main.

1
Ondra Žižka

Il n'y a pas de solution simple. La façon dont je l'ai fait par le passé est de démarrer un autre processus pour exécuter la commande ps sur des systèmes de type Unix ou la commande tasklist sous Windows, puis analyser le résultat de cette commande pour le PID souhaité. En réalité, j'ai fini par placer ce code dans un script Shell distinct pour chaque plate-forme qui venait de renvoyer le PID, afin de pouvoir conserver l'élément Java le plus indépendant possible de la plate-forme. Cela ne fonctionne pas bien pour les tâches éphémères, mais cela ne m'a pas posé de problème. 

0
Stewart Murrie

Pour les systèmes GNU/Linux et MacOS (ou généralement de type UNIX), j'ai utilisé la méthode ci-dessous qui fonctionne bien:

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

Je crois que le seul moyen portable de le faire est de lancer un processus (enfant) via un autre processus Java (parent), qui me permettra de connaître le PID du processus parent Le processus enfant peut être n'importe quoi.

Le code de ce wrapper est

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

Pour l'utiliser, créez un fichier jar contenant uniquement celui-ci et appelez-le avec les arguments de la commande, comme suit:

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

le projet jnr-process fournit cette possibilité.

Il fait partie du runtime natif Java utilisé par jruby et peut être considéré comme un prototype pour l'avenir Java-FFI

0
the8472

Une solution consiste à utiliser les outils spécifiques de la plate-forme:

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

Avertissement: Non testé, mais vous avez l'idée:

  • Appelez ps pour lister les processus,
  • Trouvez votre numéro parce que vous connaissez la commande avec laquelle vous l'avez lancé.
  • S'il existe plusieurs processus avec la même commande, vous pouvez:
    • Ajoutez un autre argument factice pour les différencier
    • Faites confiance au nombre croissant de PID (pas vraiment sûr, pas simultané)
    • Vérifiez le moment de la création du processus (pourrait être trop grossier pour vraiment différencier, pas concurrente)
    • Ajoutez une variable d’environnement spécifique et répertoriez-la avec ps.
0
Ondra Žižka

Si la portabilité n'est pas un problème et que vous voulez simplement obtenir le pid sous Windows sans trop de tracas, tout en utilisant un code testé et reconnu pour fonctionner sur toutes les versions modernes de Windows, vous pouvez utiliser la bibliothèque winp de kohsuke. Il est également disponible sur Maven Central pour une consommation facile.

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

Il existe une bibliothèque open source qui possède une telle fonction et des implémentations multi-plateformes: https://github.com/OpenHFT/Java-Thread-Affinity

L'extraction du PID est peut-être excessive, mais si vous voulez d'autres éléments, tels que le CPU et l'identifiant de thread, et plus particulièrement l'affinité de thread, cela peut vous convenir.

Pour obtenir le PID du thread actuel, appelez simplement Affinity.getAffinityImpl().getProcessId().

Ceci est mis en œuvre en utilisant la JNA (voir la réponse d’arcsin).

0
Luciano