Jar-Neustart mit geänderten Java Heap Size

roadrunner22x

Grünschnabel
Hallo hier lieben,

meine Jar-Applikation ist an den Rand des Möglichen gestoßen. Auch mir blieb nix anderes mehr möglich als das Programm mit höheren "Java Heap Size"- Werten (kurz JHS) zu starten. Aber wie?

Mit diesem Beitrag möchte ich meinen Umsetzung vorstellen, um erstens eine mögliche Gedankenstütze zu geben und zweiten euch aufwendige Recherchezeit zu ersparen.

Das Programm wird später nicht von mir genutzt und dem User erklären, dass er ein Batch/Shell starten soll oder wo möglich noch eine Konsole öffnen muss und dort was rein hacken, geht mir persönlich zu weit und hat nix mit Usability zu tun.

Aus diesem Grund habe ich mir, danke dem Background-Wissen von "tutorials.de" und "google", eine kleine Klasse geschrieben, die durch den Aufruf
Code:
public static void main(String[] args)
{	
	SystemHeapSpace.checkNeedToRestart(args);
	...
}
prüft, ob die aktuelle JHS der JVM größer gleich der gewünschten JHS ist. Sollte dies nicht so sein, wird ein Restart der eigenen Applikation, mit den neuen JHS-Werten, erzwungen.

Aber da kommen wir schon zur eigentlichen Problematik. In Java ist es momentan(vielleicht später mal) nicht möglich, einem laufenden Programm zusagen "schließe dich und starte neu". Damit meine ich nicht, gaphisch was neu zeichen oder so, sondern einen sauberen Neustart, wo möglich mit neuen Parametern für das Programm oder für die JVM.

Durch einen Workaround über eine Skript-Datei kann dies aber erwirkt werden. Dazu wird eine temporäre Skript-Datei geschrieben, welche den Befehl zum Start der Applikation beinhaltet. Danach wird diese in einem separaten Prozess aufgerufen und die alte Applikation beendet. Mit dem Start des neuen Programm, wird das temporäre Skript wieder gelöscht.

Jetzt werden einige sagen: "Java ist/soll plattformunabhängig sein!". Nun ja, aber irgendwann kommt man bei großen Projekten an den Punkt, wo es einfach nicht mehr geht und für jedes Betriebssystem spezifische Module entwickelt werden müssen. Das gleiche wurde auch hier umgesetzt bzw. muss noch umgesetzt werden. Jedes OS hat seine eigene Skriptsprache. Sei es durch eine Batch (Win), Shell (Linux) etc.

Die hier beschriebene Klasse wurde erst mal nur für Windows implementiert. Allerdings kann sie an den kommentierten Stellen entsprechende erweitert werden.

Code:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
 * @author roadrunner22x
 *
 */
public class SystemHeapSpace {

	/**
	 * start java heap size
	 */
	private final static String initJavaSize = "128m";
	/**
	 * Maximum java heap size
	 */
	private final static String maxiJavaSize = "512m";
	/**
	 * Reference threshold at the desired maximum heap size
	 */
	private final static long referenceMaxJavaSize = 400000000;
	
	/**
	 * This method examines the need for a restart. This is based on the size of the maximum java heap size of 
	 * JVM and the desired maximum java heap size.
	 * @param parameters - arguments on startup
	 * @return
	 */
	public static boolean checkNeedToRestart(String [] parameters)
	{
		delTempBatchFile();
		if (getMaxHeapSpace() < referenceMaxJavaSize)
		{
			try {
				restart(parameters);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return false;
	}
	
	/**
	 * This method returns the maximum Java Heap Space size.
	 * @return maximum Java Heap Space as long
	 */
	public static long getMaxHeapSpace(){
		return Runtime.getRuntime().maxMemory();
	}
	
	/**
	 * This method starts the newly downloaded java file and terminate the current program.
	 * @throws IOException 
	 */
	private static void restart(String [] parameters) throws IOException
	{
		String command = filterOnOSSystem(parameters, System.getProperty("java.class.path"));
        
        Runtime.getRuntime().exec(command);
      
        System.exit(0);
	}
	
	/**
	 * This method filters the current operting sytem and calls the corresponding sub-function.
	 * @param parameters - arguments on startup
	 * @param jarFile - jar file path/ jar file name
	 * @return start instructions for your operating system
	 * @throws IOException
	 */
	private static String filterOnOSSystem(String [] parameters, String jarFile) throws IOException
	{
		if (System.getProperty("os.name").toLowerCase().startsWith(new String("windows")))
			return winScriptFile(parameters, jarFile);
		else if (System.getProperty("os.name").toLowerCase().startsWith(new String("linux")))
			return linuxScriptFile(parameters, jarFile);
		/*
		 * diese "wenn, dann, sonst" Methode kann durch weiter Abfrage andere Betriebsysteme prüfen,
		 */
		else
			return null;
	}
	
	/**
	 * This method write a temporay batch file for restart the application in a microsoft windows system.
	 * @param parameters - arguments on startup
	 * @param jarFile - jar file path/ jar file name
	 * @return a microsoft windows start instructions
	 * @throws IOException
	 */
	private static String winScriptFile (String [] parameters, String jarFile) throws IOException
	{
		/*
		 * Inhalt der Batch-Datei erzeugen
		 * Dabei ist darauf zu achten, das bei einer Parameter-Mitgabe ein extra Fenster
		 * angezeigt werden muss. Dies wird durch den Unterschied mit "java" und javaw"
		 * erzeugt.
		 */
		StringBuilder param = new StringBuilder();
		for (String para: parameters){
			param.append(" "+para);
		} 
		StringBuilder command = null;
		if (parameters.length > 0)
			 command = new StringBuilder(	"start java " +
			 									"-Xms"+initJavaSize+" " +
			 									"-Xmx"+maxiJavaSize+" " +
			 									"-jar " +
			 									jarFile + param + "\r\n" +
											"exit");
		else
			 command = new StringBuilder(	"start javaw " +
			 									"-Xms"+initJavaSize+" " +
			 									"-Xmx"+maxiJavaSize+" " +
			 									"-jar " +
			 									filterJarFileName(jarFile) + "\r\n" +
											"exit");
		/*
		 * Datei anlegen und beschreiben
		 */
		File fWin = new File("tempBatch.bat");
		FileWriter fwWin = new FileWriter(fWin);
		fwWin.write(command.toString());
		fwWin.flush();
		fwWin.close();
		return "cmd /C start /MIN " + fWin.getName() + " exit";
	}
	
	/**
	 * This method write a temporay batch file for restart the application in a linux system.
	 * @param parameters  - arguments on startup
	 * @param jarFile - jar file path/ jar file name
	 * @return a linux start instructions
	 * @throws IOException
	 */
	private static String linuxScriptFile (String [] parameters, String jarFile) throws IOException
	{
		StringBuilder command = new StringBuilder();
		/*
		 * Hier müssen jetzt die Befahle für linux hin
		 */
		File fLinux = new File("tempBatch.sh");
		FileWriter fwLinux = new FileWriter(fLinux);
		fwLinux.write(command.toString());
		fwLinux.flush();
		fwLinux.close();
		return null;
	}
	
	/**
	 * This method filters out from the given jar file path to the file name.
	 * @param jarFileName - jar file path
	 * @return jar file name
	 */
	private static String filterJarFileName(String jarFileName)
	{
		int lastPathFind = 0;
		/*
		 * bei Doppelklick auf ein Jar-File gibt "System.getProperty(java.class.path)"
		 * den gesamten Dateipfad aus. Dieser muss erst weg rationalisiert werden.
		 */
		if (-1 == (lastPathFind = jarFileName.lastIndexOf(System.getProperty("file.separator"))))
			lastPathFind = 0;
		else
			lastPathFind++;
		return jarFileName.substring(lastPathFind, jarFileName.length());
	}
	
	/**
	 * This method delet all temporary files.
	 */
	private static void delTempBatchFile() {
		File f = new File("./");
		File [] fList = f.listFiles();
		for (File tempF : fList){
			if (tempF.getName().equals("tempBatch.bat"))
				tempF.delete();
			if (tempF.getName().equals("tempBatch.sh"))
				tempF.delete();
			/*
			 * Hier kann noch erweitert werden, durch weitere Skript-Files
			 * anderer Betriebsysteme. 
			 */
		}	
	}
}

Durch die Modifizierung dieser JHS-Werte kann diese Prüfung entsprechend geändert werden.
Code:
/**
 * start java heap size
 */
private final static String initJavaSize = "128m";
/**
 * Maximum java heap size
 */
private final static String maxiJavaSize = "512m";
/**
 * Reference threshold at the desired maximum heap size
 */
private final static long referenceMaxJavaSize = 400000000;


Wie bereits erwähnt, ist die hier vorgestellte Klasse vorrangig für Windows ausgelegt. Kann aber beliebig erweitert werden. Wer Verbesserungsvorschläge hat, kann gerne seine Meinung hierzu schreiben.

Ich hoffe ich konnte dem einen oder anderen weiter helfen.

Liebe Grüße
de roadrunner ;-):p
 
Dazu hätte ich eine Frage ;)

Es gibt Zahlreiche Projekte die sich mit dem Starten von Java Anwendungen beschäftigen, hast du dich mit denen beschäftigt?

In genau diesen kann man zum Beispiel Windows typischer eine EXE generieren welche mit definierten Heap Size Startet.
Wobei dein weg eleganter ist ;)

Oder zum Beispiel die Anwendung mit Scripten auszugeben welche ebenso die Anwendung starten.
Die Scripte die man Im Vorfeld schreiben kann, welche der User einfach nur starten muss um die Java Anwendung zu Starten. Und die Scripte von dem Programm aus zu modifizieren ist auch möglich. Wobei dieses deiner Lösung ja gleich kommt.

Um die Unabhängigkeit zu gewährleisten könnte man aus der Anwendung heraus das OS ermitteln und die nötigen Scripte zu manipulieren.
 
Hallo,

@wakoz:
Es gibt Zahlreiche Projekte die sich mit dem Starten von Java Anwendungen beschäftigen, hast du dich mit denen beschäftigt?
Dazu muss ich dir sagen, dass mir bei der Recherche, kein Projekt aufgefallen ist, dass entsprechend meiner Vorstellungen die Problematik umgesetzt hat.

...die Anwendung mit Scripten auszugeben welche ebenso die Anwendung starten. ...
Die Prämisse liegt darauf, dass dem User nur eine Datei zugesendet wird. Dies hatte ich versucht zu sagen durch die Worte:
...dass er ein Batch/Shell starten soll oder wo möglich noch eine Konsole öffnen muss und dort was rein hacken, geht mir persönlich zu weit und hat nix mit Usability zu tun.
Des Weiteren muss ich dich mal fragen, ob du überhaupt eine Zeile meines Beitrages gelesen hast. Dabei wäre dir aufgefallen, das die Klasse bereits temporäre Skript zum Programmneustart nutzt.

Um die Unabhängigkeit zu gewährleisten könnte man aus der Anwendung heraus das OS ermitteln und die nötigen Scripte zu manipulieren.

Wenn du meinen Beitrag richtig gelesen hättest, dann wäre dir aufgefallen, dass ich an mehreren Stellen erwähnt habe, dass die Klasse nicht nur ausschließlich für Windows-System konzipiert ist. Du solltest dir mal die Methode filterOnOSSystem(String [] parameters, String jarFile) anschauen. Dort wird das OS ausgelesen und entsprechend weiter verfahren.

Ich danke dir für deinen Beitrag, dennoch bitte ich dich, dass nächste Mal genauer zu lesen, bevor du meinst etwas schreiben zu müssen.

@all:
Die Klasse funktioniert ausschließlich mit JARs. Während der Entwicklung habe ich "eclipse" genommen. Bei dieser IDE, kann man in den Properties den HeapSize einstellen

Gruß
de roadrunner ;-):p
 
Was spricht denn dagegen den HeapSpace (-xmx) direkt höher anzusetzen?
Wenn die Anwendung doch sowieso mit höheren HeapSpace (neu-)gestartet wird.

Außerdem:
In der netbeans.conf steht folgendes:
# Note that a default -Xmx is selected for you automatically.

Möglicherweise hilt dir da der Code von NetBeans weiter...
 
Was spricht denn dagegen den HeapSpace (-xmx) direkt höher anzusetzen?

Dann musst du mir mal bitte erklären, wenn man nur eine Jar-File vorliegen hat, wie man standardmäßig einen höheren JavaHeapSize angeben kann. Na da bin ich mal auf deine Antwort gespannt.

Es ist mir schon bekannt, dass man wärend der Programmierung mit "Eclipse" oder "Netbeans" einen höheren HeapSize in den Parametern angeben kann. Nun hilft das aber nix, wenn das Projekt fertig ist und man nur noch mit der ausführbaren Datei rum rennt.

Lieben Gruß
de roadrunner ;-):p
 
Du wirst nicht um ein Script oder ApplStarter herumkommen.
Erstell ein zweites JAR, welches das Erste mit den gewünschten Parametern startet.
Das ApplStartet-Jar und dein echtes JAR könntest du auch in ein JAR packen!
Somit hast du nur ein JAR (inkl. ApplStarterClass).
 
Hallo roadrunner22x,

ich finde deine Lösung super. :)
Noch besser finde ich, daß du sie anderen zur Verfügung stellst.
Und laß die Trolle, die deinen Beitrag nicht mal richtig gelesen haben, einfach links liegen.
Was die da von sich geben ist einfach nur peinlich.

MfG
hansmueller
 
@roadrunner

danke für den Tipp, aber du weißt anscheinend gar nicht was du geschrieben hast;)

Im ersten Beitrag schreibst du

Wie bereits erwähnt, ist die hier vorgestellte Klasse vorrangig für Windows ausgelegt. ....
jetzt soll es auf einmal Plattform unabhängig sein?

Des weiteren habe ich auf deine Methode ein Script zu manipulieren Bezug genommen, nur finde ich es umständlich das Programm einmal zu starten um es dann Neuzustarten nur damit der heapsize stimmt. Man könnte auch anstelle einer Tempfile eine normale Datei beim ersten Start erzeugen lassen und dies als INI immer wieder verwenden ohne bei jedem Programmstart zweimal zu starten. Dazu kommt das was benhaze geschrieben hat.

Zip archive kann jeder entpacken und es wäre im ersten Moment immer noch eine Datei.
es gibt zahlreiche Installer die alle datein entpacken, also eine Setup Datei zusenden.
Alternativen brauchen wir nicht ansprechen, denn diese machen alles Plattform abhängig.


Und nur weil du nicht verstehst was ich sagen will, heißt es nicht das ich deinen Beitrag nicht versehe ;)


die Möglichkeit die Heapsize im "Laufenden" System zu manipulieren finde ich klasse, nicht zuletzt weil man nicht immer die Heapsize vorher kennen kann. Ich denke da an Apache POI.
Aber dein Weg ist umständlich und die Verbesserungs Ideen von uns musst du nicht kaputt schreiben.
 
@HansMueller
Und laß die Trolle, die deinen Beitrag nicht mal richtig gelesen haben, einfach links liegen.
Was die da von sich geben ist einfach nur peinlich.

MfG
hansmueller

Da fühle ich mich direkt angesprochen.
Nur verstehe ich nicht wieso *Troll* und *peinlich*?
Ist das hier wirklich dein einziger Beitrag zu diesem Thema?
Dein Beitrag ist einfach nur wertlos!
Sei doch wenigstens so nett und gib uns einen weniger *peinlichen* Tipp.

@roadrunner22x
Erstell doch einfach eine zweite Klasse (auch mit Main-Methode), die dein JAR mit den Parametern startest, die du brauchst.

anders ausgedrückt:

java -jar deineApp.jar
macht folgendes:
java -cp %DEIN_CP% -Xmx 500m -dein.classpath.zur.echten.anwendung.ClassMitMainMethode

Damit hast du nur eine JAR-Datei, allerdings mit 2 Main-Methoden.
MainMethode1 erstellt einen neuen Java-Prozess mit MainMethode2.
MainMethode1 ist in der JAR-Manifest deklariert.

Den Code zum Starten hast du ja schon.

Wenn dein JAR eh nur unter Windows Verwendung findet, kannst du auch LAUNCH4J benutzen.
 
benhaze hat gesagt.:
Sei doch wenigstens so nett und gib uns einen weniger *peinlichen* Tipp.
Ok... dann werde ich mal was versuchen, aber bitte nicht zu viel erwarten.

Ich würde ProcessBuilder.start() statt Runtime.getRuntime().exec() verwenden. Habe mal irgendwo gelesen, daß ProcessBuilder neuer und besser sein soll.

Auch würde ich nicht extra ein Script auf die Platte schreiben und dieses dann ausführen.
Ich würde alle Komandos der Reihe nach in den ProcessBuilder stopfen. Das klappt ganz gut. Habe mir mal auf diese Art ein (Java-)Programm geschrieben, daß meine java-Dateien kompiliert, in eine Jar packt und diese Jar anschließend signiert. (Natürlich war für jeden dieser Schritte ein eigener ProcessBuilder notwendig.)

Eine ganz andere Möglichkeit wäre evtl. WebStart. Da kann man gleich für den Programmstart diverse Parameter festschreiben (über die JNLP-Datei).
Kommt halt darauf an, wie man das Programm vertreiben will.

@benhaze: Eine Jar mit 2 Main-Methoden, eine zum Programmstarten und eine für das eigentliche Hauptprogramm - auch eine Möglichkeit. Kann man da aber auch noch eine Möglichkeit einbauen, falls der Anwender bereits so schlau war, von sich aus die JHS zu erhöhen? (Die meisten Anwender sind DAUs, aber eben nicht alle.) Da finde ich den Lösungsansatz von roadrunner22x etwas eleganter, da alles in ein einziges Programm gepackt ist.

MfG
hansmueller
 

Neue Beiträge

Zurück