Applikation updaten

S

SPiKEe

moin
gleich vorweg : ich beziehe mich auf folgendes posting
http://www.tutorials.de/enterprise-...-zugriff-auf-den-code-des-hauptprogramms.html
da die letzte frage ... die möglichkeit der updates ... nicht beantwortet wurde und das thema doch schon älter ist wollt ichs jetzt nich noch mal ausgraben sondern der übersicht halber in neues thema machen
*ps : im verlinkten post wurde von Thomas auf weitere postings verwiesen ... der erste ist der den ich mal losgetreten habe ... aber ich habe mich dann für das plugin-system selbst für eine mischung aus meinem und dem geposteten link des thread-erstellers entschieden*

zu meiner frage : ich möchte hier nicht diskutieren wie man nun einzelne plugins komplett unloaded und alle referrenzen entfernt so das GC greifen kann ... nur um dann die einzelene daten zu aktuliesieren ... das ist kein problem für mich
ich würde gerne wissen wie das mit der main-app funktioniert ...

zenario :
main-class > enthält MAIN
GUI-classes
plugin-handler / -manager > für das laden / entladen , die verwaltung und kommunikation zuständig
plugins > im unter-ordner "plugins" in JAR-files

die main-class , die GUI-classes und die plugin-handler befinden sich in einem main-JAR ... *falls hier schon eine aufteilung nützlich wäre bitte erwähnen*

zum ablauf : main-class soll vor laden der GUI ein plugin initialisieren welches für das update des main-JARs zuständig ist ...
damit nun aber das main-JAR aktulisiert werden kann müssen ja alle referrenzen auf alle klassen dieses JARs auf NULL gesetz und durch den garbagecollector freigegeben werden ...
das wäre auch noch kein problem in dem nach der initialisierung des update-plugins ein thread in diesem gestartet wird der die main-class darauf überwacht das alle referrenzen in der main-class aufgehoben wurden ... dann entfernt er seine eigene referrenz auf die main-class und damit sollte das main-JAR nun frei sein *nach einem expliziten aufruf von System.gc(); *ist das an der stelle überhaupt notwendig ?*
da ja nun alle referrenzen auf das main-JAR entfernt wurden sollte es für den update-thread welcher referrenzlos weiterlaufen sollte *ich weis nich ob das wirklich so is* möglich sein das main-JAR zu löschen ... *ich habe es noch nicht ausprobiert ... werde es auch heute abend nicht mehr schaffen ... aber im laufe der nächsten woche dann*

der update-thread soll *BEVOR das main-JAR gelöscht wird* nun das main-JAR via netz auf neue version prüfen , wenn nötig runterladen und "installieren" *im sinne von altes jar löschen und neues einfügen*
nun soll der update-thread wieder eine instanz der main-class bilden und in einer methode einsteigen die NACH dem update liegt *sonst wäre das ein wunderschöner deadlock*
das neu-instanzieren und einspringen an einem gewünschten punkt ist ja nicht das problem

meine frage dazu ist nun eig einfach : ist es möglich das ein JAR sich quasi selbst updatet in dem es über andere JARs und threads geht um alle referrenzen auf sich selbst zu löschen so das es komplett freigegeben und löschbar ist ?
wenn ja : auch so wie ich es oben versucht habe zu beschrieben ?
wenn nein : welche möglichkeiten bleiben ?
eine verwendung von ProcessBuilder bzw Runtime.exec() will ich eigentlich vermeiden ... warum ? ... weil es unter unix z.B. kein JAVAW gibt ... sondern auch für reine GUI-apps ohne console JAVA verwendet wird ...
da ich aber gerne plattformunabhängig bleiben will will ich das ganze mit java-internen mitteln lösen

danke schon mal im vorraus für das zerbrechen eurer köpfe ...

ps : werde wie gesagt mich mal nächste daran versuchen und bei erfolg meinen source posten
 
ich habe es jetzt mal mit einer beispiel-implementation versucht ...
ergebnis > der update-thread wartet vergebens auf das freigeben des app.jar und hängt damit in der while fest die zum löschen dient
Java:
while(!f.delete()) { }
ich habe in der app.jar und der darin enthaltenen class alle instanzierten objekte auf NULL gesetzt und anschließend explizit System.gc(); aufgerufen
scheinbar reicht das nicht um das JAR komplett frei zu geben ...
ist es weil der ClassLoader der MAIN noch auf diese zeigt und damit eben nicht alle referrenzen restlos beseitigt werden ?
ich stehe aufm schlauch ...
wäre froh wenn jemand ne lösung hätte ...

ja ... es wäre zwar möglich mit einem dritten davorgeschalteten JAR das ganze zu machen ... aber dann kann ich eben dieses LOADER-JAR wiederum nicht löschen / update ...
gibt es also ne möglichkeit über threads *oder anders* das main-jar komplett freizugeben so dass es gelöscht werden kann ? ... wenn nein : welche problemlösung würdet ihr hier vorschlagen
hilfe , fragen und kritik sind willkommen
 
Hi,

Prüf mal, ob das JarFile in Deinem Classpath liegt. Wenn ja, kannst Du es imo nicht löschen. Einzige Möglichkeit aus Deinem Main heraus einen neuen Classloader zu instantiieren und diesen dann für den eigentlichen Programmstart zu nutzen. Wenn Du per Update Dein altes Jarfile löschen willst, kannst Du das dann tun, weil Du nur noch die Classloaderreferenz löschen musst, um das alte jar zu löschen (hypothese).

Wichtig ist, dass Du keine Referenzen aus Deinem Launcher auf dein Jar hast, ausser über den Classloader und der geholten Klasse.

Grüße
gore

[edit] : da kannst das mit jmap und jhat oder eclipse MAT verifizieren (nach lösen der Classloaderref und gc() dürfen keine jar-only-classes mehr im heapdump herumfliegen).

[edit-2] : du solltest Dir generell Gedanken machen, ob Du wirklich löschen willst, oder einfach eine aktuelle Version Deines JARs installieren möchtest. Moderne Buildwerkzeuge wie maven integrieren per default immer eine Versionsnummer, was das koexistente Vorhalten von JARs unterschiedlicher Version begünstigt.
 
Zuletzt bearbeitet:
danke für die antwort
zu deinen fragen

CP : ja ... JAR liegt im Classpath da dieser bei mir standardmäßig auf
Code:
CLASSPATH=.;H:\java
eingestellt ist ... heißt also das ich erstmal meinen CP ändern müsste damit ich das jar frei bekomme ? wäre umständlich wenn ichs verteile sicherzugehen das nicht bei jemanden dann der CP zufällig ein verzeichnis includierd in den dann das jar gespeichert wird

referrenzen / launcher : ich glaube da hast du etwas missverstanden ...
ich habe deutlich erwähnt das ich KEINEN launcher möchte ...
aus dem grund da ich dann eben genau diesen launcher selbst auch wiederum nicht updaten kann ...
was die referrenzen an sich angeht : natürlich lösch ich die alle und führe GC aus ... und es sind auch alle objekte null ... trotzdem lässt sich das jar nicht löschen

IDE : ich verwende keinerlei IDE's wesshalbe sowas für mich unwichtig ist ...
was die versionierung angeht habe ich bereits ein konzept was co-existenz sicher vermeidet

das mit der instanzierung eines neuen classloader an sich ist sicherlich irgendwie logisch wenn man aus einer klasse komplett aussteigen möchte ... oder verstehe ich hier was falsch ?
könnte es vllt auch daran liegen das ich das über den classloader ein objekt mit newInstance erzeuge welches dann als Thread gecastet und gestartet wird ...
ich mein soweit ich java verstanden habe über die jahre wird doch dann dieser thread als child-thread des main-threads angesehen was das beenden des main-threads unmöglich macht da noch ein child-thread läuft

ich werd es auf jeden fall mal mit dem classpath versuchen ...
sollte sich dadurch nichts ergeben werd ichs noch mal posten ...

ergebnis vom test :
also wenn ich nur den classpath entferne *also einfach übers SET command* kommt es aufs selbe raus als wenn ich aus nem anderen verzeichnis herraus starte *java -jar <path>\app.jar* ... ergebnis bleibt leider das gleiche

das lässt mich zu der annahme kommen das alleine durch den aufruf von java mit angabe des JAR dieses als referrenz im java-internen classloader *also eben dem main-loader* erhalten bleibt

es sei noch mal gesagt : ich möchte auf einen loader / launcher und/oder EXE-wrapper sowie auf den gebraucht von Runtime.exec() / ProcessBuilder verzichten ...
ich dachte halt das es alleine mit THREADS geht ... aber das dem nicht so ist hab ich ja nu festgestellt
hat noch jemand eine idee wie man das MAIN-jar welches man beim aufruf von java angibt und aus dem die app gestartet wird löschen und ersetzen *halt updaten* kann ohne das der eigentlich programmfluss dabei unterbrochen wird *es soll also nicht aus der app ausgestiegen und i-wie neu gestartet sondern mit nur einem aufruf das ganze realisiert werden*

bitte um hilfe und ideen ... und auch hints *wie das man keine jars löschen kann die im CP stehen - thx für die info .. wusste ich noch nicht*

SPiKEe
 
Das mit dem Launcher hast Du wohl missverstanden. Dein Launcher ist eine Minikomponente in einem eigenen Jar-File, welche einen Classloader instantiiert, Dein Applikationsjar nachlädt, die Klasse instantiiert und dann Deine Startroutine auslöst. Dies geschieht alles in der selben JVM und Du brauchst keinen ProcessBuilder, Threads o.ä..

Was Du bedenken musst, ist die Tatsache, dass Dein root classloader samt main() läuft und damit Referenzen in Dein Jar-File hält, womit dieses Jar-File nicht löschbar ist.

Es geht hierbei weniger um den Classpath, sondern viel mehr um die Tatsache, dass das Jar benutzt wird und demnach nicht überschrieben werden kann.

Dabei hilft Dir auch nicht die Tatsache, dass Du keine Objekte mehr hältst, da die im Jarfile enthaltenden Klassendefinitionen im aktiven Classloader verbleiben (Heap vs Perm)


Grüße
gore
 
aaah ... das ist doch mal ne antwort die mich endlich schlauer macht und mich meiner lösung näher bring
Was Du bedenken musst, ist die Tatsache, dass Dein root classloader samt main() läuft und damit Referenzen in Dein Jar-File hält, womit dieses Jar-File nicht löschbar ist.
genau das wollte ich doch wissen ...
ich hatte so etwas schon vermutet war mir aber nicht sicher ...
was meine aussage über die thread - child-thread - theorie angeht > SCHWACHSINN ... man kann threads aus einem anderen heraus starten ... diese laufen dann unabhängig von ein ander *nur als info für alle die es NICHT wussten*

das mit dem loader ist wie gesagt immo der streipunkt zwischen uns beiden ...
WAS du damit meintest war mir klar ... ich wollte dir lediglich zu verstehen geben das ich genau so etwas eigentlich vermeiden wollte aus dem grund da ja dann eben genau dieser loader wiederum nicht updatebar ist *halt aus den gründen mit dem root-classloader und so*
aber wie es aussieht werde ich wohl um genau diesen loader nicht herrum kommen ... dann muss ich das ganze halt so für den RC basteln das ich auch noch in jahren dieses konzept nicht verändern werde ( / muss)

ich danke dir hiermit noch mal für die info und die eindeutige ... nicht missverständliche antwort ...


PROBLEM GELöST > THREAD ERLEDiGT
*kann ~closed~ wenn ihr wollt*
 
PROBLEM NOCH NICHT GELÖST


so .. habe mir jetzt einen loader zusammen geschustert der das jar-file über einen URLClassLoader läd ... das class-object erzeugt ... dann noch ein object über newInstance() und dann über reflections die mehtod startet welche den updater starten soll
das funktioniert auch so weit und der thread im updater wird gestartet und die methoden returnen alle ...
danach werden alle referenzen auf NULL gesetzen ... GC aufgerufen ... und trotzdem bleibt der thread des updaters immer noch am löschen des files hängen ... obwohl auf dieses keinerlei referenzen mehr bestehen ...

entweder ist der aufruf über reflections das problem ... oder ich steh total aufm schlauch

n beispiel wäre nett ...

schema :
launcher > launcher.jar > läd app.jar
app > app.jar > läd im construtor *in launcher über newInstance* den pfad um diesen für den CL des updaters zu verwenden* > über public void update() den updater-thread starten *bis hierhin ****t auch alles*
updater > updater.jar > soll lediglich die app.jar löschen und das file app.new in app.jar umbennen .. dieses dann über einen URLClassLoader laden und dann in app *vorher newInstance* einen Thread starten *also newInstance muss auf Thread gecastet werden*

habe es soweit alles lauffähig ... nur hängt er beim löschen von app.jar

CODES :
launcher
Java:
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
public class launcher
{
	public static void main(String[] args) throws Exception
	{
		String _PATH=(new File(System.getProperty("java.class.path"))).getCanonicalFile().getParent();
		File uf=new File(_PATH, "app.jar");
		URI uri=uf.toURI();
		URL url=uri.toURL();
		URL[] urla=new URL[] { url };
		ClassLoader cl=new URLClassLoader(urla);
		Class<?> clazz=cl.loadClass("app");
		Object oi=clazz.newInstance();
		Method[] ma=clazz.getMethods();
		for(Method m : ma)
		{
			if(m.getName().equals("bootstrap"))
			{
				m.invoke(oi);
				System.out.println("bootstrap invoked");
				m=null;
				break;
			}
		}
		oi=null;
		ma=null;
		uf=null;
		uri=null;
		url=null;
		urla=null;
		cl=null;
		clazz=null;
		_PATH=null;
	}
}

app
Java:
import java.io.*;
import java.net.*;
public class app extends Thread
{
	String _PATH="";
	public app() throws Exception
	{
		_PATH=(new File(System.getProperty("java.class.path"))).getCanonicalFile().getParent();
	}
	public void bootstrap() throws Exception
	{
		System.out.println("start bootstrap");
		File uf=new File(_PATH, "updater.jar");
		URI uri=uf.toURI();
		URL url=uri.toURL();
		URL[] urla=new URL[] { url };
		ClassLoader cl=new URLClassLoader(urla);
		Class<?> clazz=cl.loadClass("updater");
		Thread oi=((Class<Thread>)clazz).newInstance();
		System.out.print("start updater ... ");
		oi.start();
		System.out.println("ok");
		uf=null;
		uri=null;
		url=null;
		urla=null;
		cl=null;
		clazz=null;
		oi=null;
		_PATH=null;
	}
	public void run()
	{
		System.out.println("RUNNING v1");
	}
}

updater
Java:
import java.io.*;
import java.net.*;
public class updater extends Thread
{
	String _PATH="";
	public updater() throws Exception
	{
		_PATH=(new File(System.getProperty("java.class.path"))).getCanonicalFile().getParent();
	}
	public void run()
	{
		try
		{
			File of=new File(_PATH, "app.jar");
			File nf=new File(_PATH, "app.new");
			System.out.print("wait for delete ... ");
			while(!of.delete()) { }
			System.out.println("ok");
			of=null;
			nf.renameTo(new File(_PATH, "app.jar"));
			nf=null;
			ClassLoader cl=new URLClassLoader(new URL[] { (new File(_PATH, "app.jar")).toURI().toURL() } );
			Class<?> clazz=cl.loadClass("app");
			Thread oi=((Class<Thread>)clazz).newInstance();
			oi.start();
			cl=null;
			clazz=null;
			oi=null;
			_PATH=null;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

ich weis ... _PATH sieht schlimm aus ... ist aber so der einzige weg den ich jetzt so ausm stand hab der zuverlässig den pfad zum file bestimmt um ihn im URLClassLoader zu verwenden ... wenn das eleganter geht bin ich für ne lösung offen
 
neue erkenntnisse

da ich ja das Java7-ea benutze steht mir in java.net.URLClassLoader die methode public void close(); zur verfügung ...
dies wird allerdings über sun.misc.URLClassPath realisiert ...
und das ist schon wieder ein grund diese technik nicht zu verwenden *wir alle wissen welch nachteile klassen aus den paketen sun.* bringen KöNNEN*
laut Thomas ermöglicht dies das implizite schließen von gelockten daten ...
naja habs ausprobiert und BAM ... es hat sofort ge****t *mit dem code oben* ...
nun stellt sich für mich die frage : wie nun also das gelockte app.jar explizit freigeben damit der lock rausfällt und das file gelöscht werden kann ? ...
ich möchte das ganze dann eigentlich unter J5.0 / J6 laufbar machen *warum J5.0 .. weil es immer noch leute gibt die J5.0 oder sogar noch älter *J1.4.x* aufm rechner haben ...
auf die J1.4.x-user nehme ich keine rücksicht da es J5.0 und J6 bereits lange genug gibt und mitlerweile jeder geupdated haben sollte ...
jedoch möchte ich meine user nicht dazu nötigen sich das *atm noch recht verbuggte* J7-ea laden zu müssen ... ich selber erlebe immer wieder die besten crashes damit ...

meine frage ändert sich also nun in so fern : wie kann man die durch den URLClassLoader gelockten files explizit freigeben wenn ein bloßes NULL-setzen des URLClassLoaders und der referenzen nicht ausreicht ?
gibt es da vllt wieder so einen denk-um-6-ecken - trick ? ...

bin für alle arten von antworten offen

SPiKEe
 
Warum lädst du über den Launcher zuerst die App anstatt den Updater? Das ergibt doch null Sinn. Erst Updater, der schaut, ob ein Update nötig ist, und dann erst die App starten oder halt zuerst das Update ziehen und dann die App starten. Lass die app.jar einfach in Ruhe solange nicht klar ist, ob sie gestartet oder ersetzt werden muss.
 
das mit dem "app.jar zu erst laden" entstand ja aus der idee herraus das ich keinen launcher brauchen würde / nicht haben wollte
ich habe jetzt allerdings über reflections n weg gefunden der es mir ermöglichen SOLLTE auf den launcher zu verzichten ... werde das ganze aber erst übers wochenende mal versuchen so zusammen zuzimmern ...
laut java bug-reports soll es schon leute gegeben haben die dieses problem zu zeiten des 1.3 genervt haben soll und die sich dann ClassLoader.close() selbst implementiert haben *nach dem was da so steht kann man das ganze schon mit java.lang.ClassLoader machen und nicht erst mit URLClassLoader und sub-klassen
natürlich hast du so gesehen völlig recht ... und das updaten der updater.jar würde ich dann im launcher einbauen ... aber werde vorher noch mal den weg mit reflections versuchen

SPiKEe
 
Zurück