# Externe Jar sehr langsam



## Micha2903 (13. Nov 2012)

Ich habe eine Konsolenanwendung welche ich über eine weitere Javaanwendung (noch Konsole, später GUI) starte und deren Streams (Input, Output und Error) ich im aufrufenden Programm kontrolliere.

Ich möchte damit erreichen, dass ich der Konsolenanwendung, welche nicht von mir ist, via GUI Konsoleneingaben geben kann und den entsprechenden Output bekomme.

Hier mal ein vereinfachtes Beispiel, nur zum Anzeigen des Errorstreams:


```
class Test {

    public mindcraft() throws IOException {
        System.out.println("Minecraft Tool");
        Process p = Runtime.getRuntime().exec("java -Xms1024M -Xmx1024M -jar externesProgramm.jar");
        BufferedReader buff = new BufferedReader(new InputStreamReader(p.getErrorStream()));
        String temp;
        while ((temp = buff.readLine()) != null) {
            System.out.println(temp);
        }
    }
}

public class JavaApplication {

    public static void main(String[] args) {
        new Test();
    }
}
```

Starte ich externesProgram.jar direkt in einem Command Prompt, läuft die Startroutine dieses Programms unter einer Sekunde. Starte ich es über den geposteten Code, dauert es 15 Sekunden.

Das externe Programm wird für gewöhnlich mit den Parametern -Xms1024M -Xmx1024M (wie im .exec zu sehen) gestartet. Funktioniert diese Resourcenvergabe eventuell nicht, wenn das externe Programm über ein anderes Javaprogramm aufgerufen wird, oder hat jemand sonst eine Idee, woher dieser enorme Unterschied her kommen kann?


----------



## Gast2 (13. Nov 2012)

Verwende statt Runtime besser den ProcessBuilder. Außerdem musst du die Parameter in einem String[] Array übergeben.

Gehts dir nur darum die Streams auf die Konsole umzuleiten? Das kannst du doch auch in der Konsole machen.


----------



## Micha2903 (13. Nov 2012)

Ich kann spaßenshalber mal den ProcessBuilder testen, um zu sehen, ob das einen Unterschied macht, aber wirklich vorstellen kann ichs mir nicht.

Der Aufruf in der exec war jetzt mal nur exemplarisch. Real schaut der etwas anders aus. Der Aufruf an sich klappt ja auch, ist nur eben kriechend langsam.

Ich will die Streams nicht nur auf die Konsole umleiten. Wäre auch quatsch. Da könnte ich direkt das externe Programm aufrufen.

Sinn und Zweck soll sein, dass dieses externe Programm bestimmte eingaben erlaubt, wie das manuelle zwischenspeichern, beenden des Programms u.s.w. Da ich das externe Programm nicht verändern kann, bzw. darin einen zu großen Aufwand sehe und gleichzeitig diese Eingaben später via GUI und Buttons zu realisieren, war meine Idee, das externe Programm von einem weiteren Programm aufzurufen, dass sich dann über die Streams in die Konsole "klingt".

Drücke ich also später in "meiner" GUI den Button Speichern, soll der Konsole des externen Programms die eingabe /saveall übergeben werden.

Wenn es dafür eine bessere Methode gibt als die, die ich mir bis hierhin erdacht habe, dann bin ich da natürlich offen und dankbar für Anregungen.

Für Hinweise diesbezüglich bitte ich zu bedenken, dass ich zwar kein Anfänger mehr bin, aber mich noch nich als Profi bezeichnen würde. Ein "Fundamentals of Java Programming" kann ich mein eigen nennen. Arg weit darüber hinaus geht mein Wissen aber noch nicht.



EDIT: Habe das ganze mal mit einem ProcessBuilder probiert. Ergebnis ist das gleiche.


----------



## JohannisderKaeufer (14. Nov 2012)

So wie du das machst hast du später zwei Virtuelle Maschinen am laufen. Das hat Vorteile aber auch Nachteile. Speicherbedarf, etc.

Eine Möglichkeit wäre das externe Programm in den cp mit aufzunehmen und dann dort einfach die main Methode aufzurufen, das externe Programm also wie eine lib verwenden.
Dann läuft oder läuft auch nicht alles in einer VM und Konsole.

Je nachdem wie komplex die Anwendung ist oder auch wie gut Sie geplant ist, kann es durchaus auch sehr einfach sein, sich irgendwo einzuhängen um da zusätzliche Funktionalität unterzubringen.


----------



## Micha2903 (14. Nov 2012)

Es direkt mit einzubinden wäre eine Möglichkeit. Allerdings sehe ich mich als Lernender der sehr viel mehr Interesse daran hat ein Problem erst einmal im Kern zu verstehen, anstatt direkt einen Workaround zu verwenden, ohne überhaupt zu begreifen, warum die ursprüngliche Methodik nicht funktioniert hat.

Mir fehlt da derzeit komplett der Ansatz. Mein Konsolenprogramm tut doch nicht mehr als drei Streams vom Externen Programm zu holen. Wie kann allein das dazu führen, dass ein externes, auch noch in eigener VM laufendes Programm, um den Faktor 15 langsamer wird?

Falls es als Zusatzinfo von Belang sein sollte, sei noch erwähnt, dass ich mit Netbeans 7.1 (JRE7, JDK 1.7) entwickle.


----------



## bygones (14. Nov 2012)

ehrlich gesagt seh ich solche Konsolenaufrufe eher als workaround. Sinnvoller ist es das externe jar in seiner Application einzubinden und dessen API nehmen und direkt nutzen.


----------



## Bernd Hohmann (14. Nov 2012)

Micha2903 hat gesagt.:


> Starte ich externesProgram.jar direkt in einem Command Prompt, läuft die Startroutine dieses Programms unter einer Sekunde. Starte ich es über den geposteten Code, dauert es 15 Sekunden.



Wird da viel über sysout/syserr ausgegeben? Wenn ja, lass mal die Ausgabe weg. Eventuell wirst Du einfach nur von einer lahmen Bildschirmausgabe ausgebremst.

Bernd


----------



## fastjack (15. Nov 2012)

Eigentlich braucht Du das gar nicht in der shell zu starten. Binde das Jar doch in den Classpath ein und startet dann die Main-Class. Also einfach " MeineKlasse.main()" und fertig. Die Mainklasse steht im Manifest.


----------



## Micha2903 (15. Nov 2012)

Okay. Ich werde das mal ausprobieren. Allerdings würde mich nach wie vor interessieren, wo die Performanceprobleme herkommen. Es muss doch dafür irgendeine Erklärung geben.


----------



## FerFemNemBem (15. Nov 2012)

Mahlzeit,

dann mach doch mal ein paar Debug-Ausgaben in Deinen Code, um die Codestelle einzugrenzen, die so lange braucht.

Gruss, FFNB.


----------



## Micha2903 (15. Nov 2012)

Naja. Schau dir den Code an. Sehr viel gibts da nicht. Im Grunde nur zwei Dinge. Das Abfragen einer Zeile aus dem Stream und das Anzeigen dieser Zeile auf der Konsole. Ich hab auch schon versucht dem Ganzen kleine Pausen zu geben (100ms) um auszuschließen, dass der Andauernde durchlauf der While Schleife zu sehr bremst. Dem ist aber nicht so.

Könnte das ein Shedule Problem sein? Quasi dass zwischen jedem Zyklus des Externen Programms gewartet wird, bis der Zyklus des Durchlaufs der While Schleife beendet ist? Kann mir das nicht so wirklich vorstellen, da das externe Programm ja in einer eigenen Virtuellen Maschine läuft und daher eigentlich nicht auf Kontext des Aufrufenden Programms warten sollen müsste. Oder etwa doch?


----------



## FerFemNemBem (15. Nov 2012)

Mahlzeit,

nicht Pausen reinmachen sondern (im Zweifelsfall nach jeder Zeile) ein "System.currentTimeMillis();" mit einem entsprechenden Zeilenmarker auf die Konsole Schreiben. Irgendwo muss die Zeit ja verbraten werden.

Gruss, FFNB.


----------



## Micha2903 (15. Nov 2012)

Ja das ist schon klar, aber es kann nicht sein, dass es direkt an meinem Code liegt, denn selbst wenn ich Pause von 1000ms rein bringe, ist die Sache haargenauso schnell, oder langsam. Es muss also an der Konstellation liegen.


----------



## FerFemNemBem (15. Nov 2012)

Halloechen,

Was hast Du denn bloss immer mit Deinen Pausen? Du sollst KEINE Pausen reinmachen sondern Dir die Dauer jeden einzelnen Codezeile ausgeben lassen. Dann kann man weiterschauen wo es genau hakt.

Gruss, FFNB.


----------



## Micha2903 (15. Nov 2012)

Mir ist das vollkommen klar, aber selbst wenn ich die Ausgabe komplett außen vor lasse und einfach nur die externe .jar starte, ist die kriechend langsam. Es bleibt also dabei, an meinem Code liegt es nicht.


----------



## FerFemNemBem (15. Nov 2012)

Halloechen,

aber natuerlich liegt es an Deinem Code. Das schreibst Du doch selbst:



Micha2903 hat gesagt.:


> Starte ich externesProgram.jar direkt in einem Command Prompt, läuft die Startroutine dieses Programms unter einer Sekunde. Starte ich es über den geposteten Code, dauert es 15 Sekunden.



Aber wenn Dir das zuviel ist, hier weitere Informationen zu posten (die Dich in Summe 3 Minuten kosten wuerden)...

Gruss, FFNB.


----------



## Micha2903 (15. Nov 2012)

Das ist falsch. Das Starten des Programs erfolgt über eine einzige Zeile. Besteht das Programm nur aus dieser einen Zeile, also dem Starten des Programmes, ist das externe Programm kriechlangsam. Alles was also im Code danach kommt, hat keine Einfluss auf die Geschwindigkeit.

Es geht also im Kern lediglich darum zu verstehen, warum ein irgendwie geartetes .jar Programm, gestartet aus einem anderen Programm, zu solch dermaßen rapiden Performanceverlusten führt.

Um es für dich nochmals zu verdeutlichen:

externesProgramm.jar über die Windows Konsole mit java -jar externesProgramm.jar starten und das Programm durchläuft seine Startroutine in 1-2 Sekunden.

externesProgramm.jar über ein weiteres Java Programm und den Aufruf

Process p = Runtime.getRuntime().exec("java -jar externesProgramm.jar");

starten und selbige Routine, ohne das im Aufrufenden Programm noch irgendwas getan wird und der Spaß dauert 15-16 Sekunden.


----------



## FerFemNemBem (15. Nov 2012)

Halloechen,

was bitte hindert Dich daran, mal rasch folgenden Test durchzufuehren?


```
class Test {
 
    public mindcraft() throws IOException {
        System.out.println("Minecraft Tool");
        System.out.println("1: " + System.currentTimeMillis());
        Process p = Runtime.getRuntime().exec("java -Xms1024M -Xmx1024M -jar externesProgramm.jar");
        System.out.println("2: " + System.currentTimeMillis());
        BufferedReader buff = new BufferedReader(new InputStreamReader(p.getErrorStream()));
        System.out.println("3: " + System.currentTimeMillis());
        String temp;
        while ((temp = buff.readLine()) != null) {
            System.out.println(temp);
        }
    }
}
 
public class JavaApplication {
 
    public static void main(String[] args) {
        new Test();
    }
}
```

Nach Deiner Vermutung muesste die Zeit zwischen "1" und "2" draufgehen. Das moechte ich aber gerne gesichert wissen, nicht nur als Deine Vermutung.

Gruss, FFNB.


----------



## Micha2903 (15. Nov 2012)

Nein, die Zeit geht nicht dort zwischen verloren. Ich sage ja nicht, dass das Programm an irgend einer Stelle hängen würde. Es ist insgesamt einfach viel langsamer.

Ich kann beim Startup den Speicherbedarf des externen Programmes sehr gut Nachvollziehen, da der zu beginn bei 12mb liegt, dann direkt nen Sprung auf 40mb macht und dann nochmal einen Sprung auf 400mb. Das Programm über die Windows Konsole gestartet zeigt dieses Verhalten, jedoch sehr sehr schnell, so schnell dass man es kaum wahrnehmen kann. Dabei kann in der Konsole bestimmter Output gelesen werden. Status des Ladens verschiedener Module.

Starte ich das Programm als externes im Javaprgramm, dann dauert eben dieser Startup ca 10 mal so lang. Lasse ich über die While Schleife den Output der Konsole des Externen Programms anzeigen, sehe ich auch jede einzellne Zeile an Output und die kommen nicht Ruckartig mit einem mal, oder haben verzögerungen drin. Es dauert zwischen den einzellnen Zeiten nur eben sehr viel länger.

Lasse ich nun den Output komplett weg, also Starte nur das Programm, kann ich über die Vergabe des Arbeitsspeichers sehen, dass im Hintergrund immernoch genau das gleiche Passiert.

Bitte glaube mir also, wenn ich dir sage, dass es nicht am Code liegt. Gerne zeige ich dir auch genauer über Medien wie Teamspeak, oder einen IM was genau ich da treibe. Um das hier im Forum hoch zu laden ists zum einen zu groß und zum andern zu heikel.


----------



## FerFemNemBem (15. Nov 2012)

Mahlzeit,

also nich der Start dauert lange sondern die Performance des gestarteten Prozesses ist schlecht. Ich hab das hier mal mit Deinem Code getestet (mit einem Programm, dessen Start sehr Prozessorhungrig ist und so um die 15 Sekunden dauert).


```
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

class Test 
{ 
    public Test()
    {        
        try 
        {
			Process p = Runtime.getRuntime().exec("java -Xms1024M -Xmx1024M -jar I:/Test/cputest.jar");
	        BufferedReader buff = new BufferedReader(new InputStreamReader(p.getInputStream()));
	        String temp;
	        while ((temp = buff.readLine()) != null) 
	        {
	            System.out.println(temp);
	        }
		} 
        catch (IOException e) 
		{
			System.out.println("IOException");
		}
    }
}
 
public class JavaApplication 
{
    public static void main(String[] args) 
    {
        new Test();
    }
}
```

Hier ist es voellig egal, ob ich das Programm von der Konsole oder mittels Deiner Methode starte.

Wie sieht denn bei Dir die CPU-Auslastung aus, wenn das Programm als Prozess startest? Haenge Dich doch mal mit VisualVM o.ä. an den Prozess, den Du startest (also an den Prozess von "externesProgramm.jar"). Kannst Du da im Tab "Sampler" unter "CPU" etwas auffaelliges erkennen?

Gruss, FFNB.


----------



## Micha2903 (15. Nov 2012)

Werde ich gleich morgen früh mal ausprobieren. Danke für den Hinweis und die Hilfe.


----------



## Micha2903 (17. Nov 2012)

Ich denke, ich kann das Thema nun abschließen. Manchmal sind es doch die einfachsten Dinge, die einem die Suppe versalzen und hin und wieder sieht man halt den Wald vor lauter Bäumen nicht.

Schlussendlich lag es daran, dass dieses ominöse externe .jar beim ersten Start ein paar zusätliche Aufgaben erledigt, wie das Anlegen einer Datenbank und dergleichen. Ich hatte die jar am Ursprünglichen Ort aus gestartet und dabei nicht beachtet, dass dadurch das Stammverzeichnis der externen .jar auf das des Netbeansprojekts gesetzt wird, wodurch das sämtliche Drumherum zur .jar gefehlt hat. Also die eigentlich schon vorhandenen Datenbanken und Konfigurationen. Nachdem ich die in das Projektverzeichnis kopiert hatte, geschah, was ich solange ersehnt hatte. Der Start ging in der gleichen Zeit wie beim direkten Starten der externen .jar

Ich habe die Geschichte nun auch so aufgebaut, dass ich wie vorgeschlagen die .jar in mein Projekt einbinde, was auch super funktioniert. Ich kann auch wunderbar mit dem Error und Outputstream herumhantieren, allerdings bin ich mir nicht wirklich darüber im Klaren, wie ich es hin bekomme, dass ich einen Button dazu vergewaltigen kann, etwas in den Inputstream der Konsole zu schreiben.

Aber das ist wohl ein Problem das in einen anderen Thread gehört.

Vielen Dank allen die hier versucht haben mir zu helfen. Am Ende saß einmal mehr das Problem 50cm vor dem Bildschirm.


----------



## jobima (18. Nov 2012)

Ich nehme  an das Dein im Prozess gestartetes Programm Ausgaben in die Konsole tätigt und Du daran die Zeit misst die für den Start benötig wird. 
Falls nicht wie misst Du die Zeit ?

Unter welchem Betriebssystem arbeitest Du ?


----------



## Micha2903 (18. Nov 2012)

Zwar nun nicht mehr wichtig, aber die Zeit wurde freundlicherweise vom externen Programm selbst nach der Startroutine angezeigt.


----------

