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.
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?
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.
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;
}
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) {
// ...
}
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 .
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:
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;
}
}
}
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
.
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
}
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.
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.
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;
}
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...);
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
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:
ps
pour lister les processus,ps
.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();
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).