web-dev-qa-db-fra.com

Java Runtime.getRuntime (). Exec () alternatives

J'ai une collection d'applications Web qui s'exécutent sous Tomcat. Tomcat est configuré pour avoir jusqu'à 2 Go de mémoire en utilisant l'argument -Xmx.

De nombreuses applications Web doivent effectuer une tâche qui finit par utiliser le code suivant:

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
process.waitFor();
...

Le problème que nous rencontrons est lié à la façon dont ce "processus enfant" est créé sur Linux (Redhat 4.4 et Centos 5.4).

Je crois comprendre qu'une quantité de mémoire égale à celle que Tomcat utilise doit être libre dans le pool de mémoire système physique (non permutable) initialement pour que ce processus enfant soit créé. Lorsque nous n'avons pas assez de mémoire physique libre, nous obtenons ceci:

    Java.io.IOException: error=12, Cannot allocate memory
     at Java.lang.UNIXProcess.<init>(UNIXProcess.Java:148)
     at Java.lang.ProcessImpl.start(ProcessImpl.Java:65)
     at Java.lang.ProcessBuilder.start(ProcessBuilder.Java:452)
     ... 28 more

Mes questions sont:

1) Est-il possible de supprimer l'exigence d'une quantité de mémoire égale au processus parent libre dans la mémoire physique? Je cherche une réponse qui me permette de spécifier la quantité de mémoire de l'enfant le processus obtient ou permet à Java sur linux d'accéder à la mémoire d'échange.

2) Quelles sont les alternatives à Runtime.getRuntime (). Exec () si aucune solution à # 1 n'existe? Je ne pouvais penser qu'à deux, aucune des deux n'est très souhaitable. JNI (très peu souhaitable) ou réécriture du programme que nous appelons en Java et en faisant son propre processus avec lequel la webapp communique d'une manière ou d'une autre. Il doit y en avoir d'autres.

3) Y a-t-il un autre côté à ce problème que je ne vois pas qui pourrait potentiellement le résoudre? Réduire la quantité de mémoire utilisée par Tomcat n'est pas une option. L'augmentation de la mémoire sur le serveur est toujours une option, mais semble plutôt un pansement.

Les serveurs fonctionnent Java 6.

EDIT: Je dois préciser que je ne recherche pas de correctif spécifique à Tomcat. Ce problème peut être vu avec l'une des applications Java que nous avons en cours d'exécution sur le serveur Web (il y en a plusieurs). J'ai simplement utilisé Tomcat comme exemple, car il y aura probablement la plus grande quantité de mémoire allouée à et c'est là que nous avons vu l'erreur la première fois. C'est une erreur reproductible.

EDIT: Au final, nous avons résolu ce problème en réécrivant ce que l'appel système faisait en Java. Je pense que nous avons été assez chanceux de pouvoir le faire sans faire d'appels système supplémentaires. Tous les processus ne pourront pas le faire, donc j'aimerais toujours voir une solution réelle à cela.

34
twilbrand

J'ai trouvé une solution de contournement dans ce article , l'idée est que vous créez un processus au début du démarrage de votre application avec laquelle vous communiquez (via des flux d'entrée), puis que le sous-processus exécute vos commandes pour vous .

//you would probably want to make this a singleton
public class ProcessHelper
{
    private OutputStreamWriter output;
    public ProcessHelper()
    {
        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec("Java ProcessHelper");
        output = new OutputStreamWriter(process.getOutputStream());
    }
    public void exec(String command)
    {
        output.write(command, 0, command.length());
    }
}

alors vous feriez un assistant Java

public class ProcessHelper
{
    public static void main(String[] args)
    {
         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
         String command;
         while((command = in.readLine()) != null)
         {
             Runtime runtime = Runtime.getRuntime();
             Process process = runtime.exec(command);
         }
    }
}

ce que nous avons essentiellement fait est de créer un petit serveur "exécutable" pour votre application. Si vous initialisez votre classe ProcessHelper au début de votre application, elle réussira à créer ce processus, puis vous lui acheminerez simplement des commandes, car le deuxième processus est beaucoup plus petit, il devrait toujours réussir.

Vous pouvez également approfondir un peu votre protocole, comme renvoyer des codes de sortie, signaler des erreurs, etc.

6
luke

Essayez d'utiliser un ProcessBuilder . Les Docs disent que c'est la façon "préférée" de démarrer un sous-processus de nos jours. Vous devez également envisager d'utiliser la carte d'environnement (les documents sont dans le lien) pour spécifier les allocations de mémoire pour le nouveau processus. Je soupçonne (mais je ne sais pas avec certitude) que la raison pour laquelle il a besoin de tant de mémoire est qu'il hérite des paramètres du processus Tomcat. L'utilisation de la carte d'environnement devrait vous permettre de remplacer ce comportement. Cependant, notez que le démarrage d'un processus est très spécifique au système d'exploitation, donc YMMV.

5
Ian McLaird

I pensez il s'agit d'un problème avec unix fork (), l'exigence de mémoire vient de la façon dont fork () fonctionne - il clone d'abord l'image de processus enfant (quelle que soit sa taille actuelle) puis remplace l'image parent avec l'image enfant. Je sais que sur Solaris, il existe un moyen de contrôler ce comportement, mais je ne sais pas ce que c'est.

Mise à jour : Ceci est déjà expliqué dans D'après la version du noyau Linux/libc Java Runtime.exec () sûr en ce qui concerne la mémoire?

2
Justin

Cela aiderait je pense. Je sais que c'est un vieux fil, juste pour les futures références ... http://wrapper.tanukisoftware.com/doc/english/child-exec.html

1
Kam

Une autre alternative consiste à exécuter un processus d'exécution distinct qui surveille un fichier ou un autre type de "file d'attente". Vous ajoutez la commande souhaitée à ce fichier et le serveur exec la repère, exécutant la commande et écrivant les résultats dans un autre emplacement que vous pouvez obtenir.

1
gregturn

La meilleure solution que j'ai trouvée jusqu'à présent était d'utiliser un shell sécurisé avec des clés publiques. En utilisant http://www.jcraft.com/jsch/ j'ai créé une connexion à localhost et exécuté les commandes comme ça. Peut-être qu'il a un peu plus de frais généraux mais pour ma situation, cela a fonctionné comme un charme.

1
JoG