Java Runtime.getRuntime ().exec() les solutions de rechange
j'ai une collection d'applications Web qui fonctionnent sous tomcat. Tomcat est configuré pour avoir jusqu'à 2 Go de mémoire en utilisant l'argument-Xmx.
beaucoup de webapps ont besoin d'effectuer une tâche qui finit par faire usage du code suivant:
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
process.waitFor();
...
le problème que nous avons est lié à la façon dont ce" processus enfant " est créé sur Linux (Redhat 4.4 et Centos 5.4).
c'est ce que je crois qu'une quantité de mémoire égale à la quantité que tomcat utilise doit être libre dans le pool de mémoire système physique (non-swap) 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 étant libre dans la mémoire physique? je suis je cherche une réponse qui me permet de spécifier la quantité de mémoire que le processus enfant reçoit ou de permettre à java sur linux d'accéder à la mémoire de swap.
2) quelles sont les alternatives à L'exécution.getRuntime ().exec () s'il n'existe pas de solution à #1? Je ne pouvais penser qu'à deux, ni l'un ni l'autre n'est très souhaitable. JNI (très peu souhaitable) ou réécrire le programme que nous appelons en java et en faire son propre processus que le webapp communique d'une façon ou d'une autre. Y 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 le résoudre? réduire la quantité de mémoire utilisée par tomcat n'est pas une option. Augmenter la mémoire sur le serveur est toujours une option, mais semble plus un pansement.
Les serveursutilisent java 6.
EDIT: je dois préciser que je ne cherche pas une correction spécifique à tomcat. Ce problème peut être observée avec des applications java nous avons en cours d'exécution sur le serveur (il y en a plusieurs). J'ai simplement utilisé tomcat comme exemple, car il sera très probablement avoir le plus de mémoire et c'est là que nous avons effectivement vu l'erreur la première fois. C'est une erreur reproductible.
EDIT: à la fin, nous avons résolu ce problème en réécrivant ce que l'appel système faisait en java. Je pense que nous avons eu beaucoup de chance de pouvoir faire cela sans faire de système supplémentaire. appeler. Tous les processus ne seront pas en mesure de le faire, donc j'aimerais toujours voir une solution réelle à ce problème.
6 réponses
j'ai trouvé une solution dans cet article , fondamentalement l'idée est que vous créez un processus au début du démarrage de votre application que vous communiquez avec (via des flux d'entrée) et 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 programme Java helper
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 "exec" pour votre application. Si vous initialiser votre ProcessHelper classe dès le début de votre application, il va créer avec succès ce processus, alors il vous suffit de tuyau de commandes de l'un à l'autre, parce que le deuxième processus est beaucoup plus petite, il faut toujours réussir.
vous pourriez aussi faire votre protocole un peu plus en profondeur, comme retourner exitcodes, notification des erreurs et ainsi de suite.
Essayez d'utiliser un ProcessBuilder . Les médecins disent que c'est la façon "préférée" de démarrer un sous-processus ces jours-ci. Vous devriez également envisager d'utiliser la carte d'environnement (docs 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 d'outrepasser ce comportement. Cependant, notez que le démarrage d'un processus est très spécifique à OS, donc YMMV.
je penser c'est un unix (fork () problème, le besoin de mémoire vient de la façon que fork() fonctionne, c'premiers clones le processus de l'enfant de l'image (à la taille que c'est le cas actuellement) remplace alors l'image parent avec l'enfant de l'image. Je sais que sur Solaris il y a un moyen de contrôler ce comportement, mais je ne sais pas du tout ce que c'est.
mise à jour : cela est déjà expliqué dans de ce que Linux kernel / libc la version est Java Runtime.exec() la sécurité en ce qui concerne la mémoire?
ça m'aiderait. Je sais que c'est un vieux fil, juste pour les futurs arbitres... http://wrapper.tanukisoftware.com/doc/english/child-exec.html
une autre solution consiste à avoir un processus exec distinct qui exécute un fichier ou un autre type de"file". Vous ajoutez la commande désirée à ce fichier, et le serveur exec la repère, exécute la commande et, d'une manière ou d'une autre, écrit les résultats à un autre endroit que vous pouvez obtenir.
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 avec localhost et exécuté les commandes comme ça. Peut-être qu'il y a un peu plus de frais généraux, mais pour ma situation, ça a fonctionné comme un charme.