61 votes

Comment lancer un processus totalement indépendant à partir d'un programme Java ?

Je travaille sur un programme écrit en Java qui, pour certaines actions lance des programmes externes en utilisant des lignes de commande configurées par l'utilisateur. Actuellement, il utilise Runtime.exec() et ne conserve pas le Process (les programmes lancés sont soit un éditeur de texte soit un utilitaire d'archivage, donc pas besoin des flux in/out/err du système).

Il y a cependant un petit problème : lorsque le programme Java se termine, il ne se termine pas vraiment tant que tous les programmes lancés ne sont pas sortis.

Je préférerais largement que les programmes lancés soient complètement indépendants de la JVM qui les a lancés.

Le système d'exploitation cible est multiple, Windows, Linux et Mac étant le minimum, mais tout système à interface graphique avec une JVM est vraiment ce qui est souhaité (d'où la configurabilité par l'utilisateur des lignes de commande actuelles).

Quelqu'un sait-il comment faire en sorte que le programme lancé s'exécute de manière totalement indépendante de la JVM ?


Modifier en réponse à un commentaire

Le code de lancement est le suivant. Le code peut lancer un éditeur positionné sur une ligne et une colonne spécifiques, ou il peut lancer un visualiseur d'archives. Les valeurs entre guillemets dans la ligne de commande configurée sont traitées comme étant codées ECMA-262, et sont décodées et les guillemets sont supprimés pour former le paramètre d'exécution souhaité.

Le lancement a lieu sur l'EDT.

static Throwable launch(String cmd, File fil, int lin, int col) throws Throwable {
    String frs[][]={
        { "$FILE$"  ,fil.getAbsolutePath().replace('\\','/') },
        { "$LINE$"  ,(lin>0 ? Integer.toString(lin) : "") },
        { "$COLUMN$",(col>0 ? Integer.toString(col) : "") },
        };
    String[] arr; // array of parsed tokens (exec(cmd) does not handle quoted values)

    cmd=TextUtil.replace(cmd,frs,true,"$$","$");
    arr=(String[])ArrayUtil.removeNulls(TextUtil.stringComponents(cmd,' ',-1,true,true,true));
    for(int xa=0; xa<arr.length; xa++) {
        if(TextUtil.isQuoted(arr[xa],true)) {
            arr[xa]=TextDecode.ecma262(TextUtil.stripQuotes(arr[xa]));
            }
        }
    log.println("Launching: "+cmd);
    Runtime.getRuntime().exec(arr);
    return null;
    }

Cela semble se produire uniquement lorsque le programme est lancé à partir de mon IDE. Je ferme cette question puisque le problème n'existe que dans mon environnement de développement ; ce n'est pas un problème en production . D'après le programme de test dans l'une des réponses, et les tests supplémentaires que j'ai effectués, je suis convaincu que ce n'est pas un problème qui sera vu par n'importe quel utilisateur du programme sur n'importe quelle plate-forme.

0 votes

Je pense que vous voulez dire que vous utilisez actuellement Runtime.exec()

0 votes

@Chadwick : Oups - oui, merci.

0 votes

Je pense que ce n'est qu'un problème de Windows. Sur Mac, j'ai eu le problème inverse : il fallait s'assurer que le processus externe que j'avais lancé fait sortir lorsque la JVM sort.

30voto

Ludwig Weinzierl Points 6461

Il existe une relation parent-enfant entre vos processus et vous devez la rompre. Pour Windows, vous pouvez essayer :

Runtime.getRuntime().exec("cmd /c start editor.exe");

Pour Linux, le processus semble se dérouler de manière détachée de toute façon, nohup nécessaire. Je l'ai essayé avec gvim , midori y acroread .

import java.io.IOException;
public class Exec {
    public static void main(String[] args) {
        try {
            Runtime.getRuntime().exec("/usr/bin/acroread");
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Finished");
    }
}

Je pense qu'il n'est pas possible de le faire avec Runtime.exec d'une manière indépendante de la plateforme.

pour les systèmes compatibles POSIX :

 Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "your command"}).waitFor();

1 votes

J'espérais qu'il y aurait quelque chose que je pourrais faire dans la création du processus... cela nécessite que l'utilisateur configure la "bonne" ligne de commande. Mais c'est bon à savoir.

0 votes

Vous pouvez demander à l'utilisateur de configurer la ligne de commande correcte et d'ajouter furtivement la magie spécifique au système d'exploitation si nécessaire.

0 votes

catch (IOException e) ne remplace ni l'un ni l'autre .waitFor() ni InterruptedException

25voto

Manvendra Gupta Points 103

J'ai quelques observations qui pourraient aider d'autres personnes confrontées à un problème similaire.

Lorsque vous utilisez Runtime.getRuntime().exec() et que vous ignorez le handle java.lang.Process que vous obtenez en retour (comme dans le code de l'affiche originale), il y a une chance que le processus lancé se bloque.

J'ai rencontré ce problème dans l'environnement Windows et j'ai trouvé le problème dans les flux stdout et stderr. Si l'application lancée écrit dans ces flux et que le tampon de ces flux se remplit, l'application lancée peut sembler se bloquer lorsqu'elle essaie d'écrire dans ces flux. Les solutions sont les suivantes :

  1. Capturez l'identifiant du processus et videz les flux continuellement - mais si vous voulez mettre fin à l'application java juste après avoir lancé le processus, ce n'est pas une solution réalisable.
  2. Exécuter l'appel de processus comme cmd /c <<process>> (ceci est uniquement pour l'environnement Windows).
  3. Suffixe la commande process et redirige les flux stdout et stderr vers nul en utilisant ' commande > nul 2>&1 '

2 votes

Merci. Cela a totalement fonctionné pour moi dans le cas du lancement de clients kryonet. L'option 1 peut être mise en œuvre en utilisant la méthode inheritIO de ProcessBuilder. Elle envoie tout le contenu du flux au processus Java parent. Il n'est donc pas nécessaire de rendre les processus indépendants.

2 votes

J'ai eu un problème en codant un luncher personnalisé où les processus enfants (principalement chrome et pidgin) se figent après avoir fonctionné pendant plusieurs heures, mais dès que le luncher se termine, ils se débloquent immédiatement. J'ai cherché sur Google et débogué pendant une semaine, et c'est la meilleure réponse à mon problème que j'ai trouvée. Si c'était aussi mon problème, je vous donnerais 100 000 points pour cette réponse.

0 votes

ss64.com/nt/cmd.html donne des informations supplémentaires sur la manière de passer les paramètres.

23voto

monceaux Points 396

Il peut être utile de poster une section de test du code minimal nécessaire pour reproduire le problème. J'ai testé le code suivant sur Windows et un système Linux.

public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
        Runtime.getRuntime().exec(args[0]);
    }
}

Et testé avec les éléments suivants sur Linux :

java -jar JustForTesting.jar /home/monceaux/Desktop/__TMP/test.sh

où test.sh ressemble à :

#!/bin/bash
ping -i 20 localhost

ainsi que ceci sur Linux :

java -jar JustForTesting.jar gedit

Je l'ai testé sous Windows :

java -jar JustForTesting.jar notepad.exe

Toutes ces applications ont lancé les programmes prévus, mais l'application Java n'a eu aucun problème pour se terminer. J'ai les versions suivantes de la JVM de Sun, telles que rapportées par java -version :

  • Windows : 1.6.0_13-b03
  • Linux : 1.6.0_10-b33

Je n'ai pas encore eu l'occasion de le tester sur mon Mac. Peut-être y a-t-il une interaction avec d'autres codes dans votre projet qui n'est pas claire. Vous pouvez essayer cette application de test et voir quels sont les résultats.

0 votes

Il se peut que ce problème soit limité à Windows XP, ou qu'il ne soit que le résultat de la façon dont la JVM est lancée à partir de mon IDE pendant les tests. Sous Windows Vista, ce problème ne se produit pas pour la version distribuée. Après avoir testé sur XP, cette réponse sera acceptée si elle montre un problème également uniquement lorsque la JVM est lancée depuis l'IDE.

0 votes

Il semble que cela ne se produise que lorsqu'il est lancé à partir de mon IDE. J'accepte donc cette réponse comme indiquant que mon problème n'existe que dans mon environnement de développement ; ce n'est pas un problème en production.

3 votes

Monceaux : Merci, BTW, c'est ce simple test qui m'a fait regarder de plus près les conditions précises dans lesquelles le programme ne se terminait pas - d'où j'ai réalisé que c'était mon IDE et non pas Java qui ne sortait pas.

2voto

Charlie Martin Points 62306

Vous voulez lancer le programme en arrière-plan, et le séparer du parent. J'envisagerais nohup(1) .

1 votes

Eh bien, pour Linux, bien sûr... Mais le problème n'est pas que le processus lancé soit terminé, c'est que la JVM ne se termine pas tant que le(s) programme(s) lancé(s) n'est (ne sont) pas également terminé(s).

1 votes

Avez-vous vraiment essayez le ? Un processus d'arrière-plan nohup'ed doit apparaître à l'appel system() comme s'il s'était immédiatement terminé.

0voto

Matthew Flaschen Points 131723

Je pense que cela nécessiterait un véritable fork du processus. Fondamentalement, l'équivalent C de ce que vous voulez est :

pid_t id = fork();
if(id == 0)
  system(command_line);

Le problème est que vous ne pouvez pas faire un fork() en Java pur. Ce que je ferais, c'est :

Thread t = new Thread(new Runnable()
{
    public void run()
    {
      try
      {
          Runtime.getRuntime().exec(command);
      }
      catch(IOException e)
      {           
          // Handle error.
          e.printStackTrace();
      }
    }
});
t.start();

De cette façon, la JVM ne se fermera pas, mais il n'y aura pas d'interface graphique et l'empreinte mémoire sera limitée.

1 votes

C'est exactement le problème que l'O.P. essaie d'éviter.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X