Jar überschreiben

MS-Tech

Erfahrenes Mitglied
Hallo Zusammen,

ich denke das Thema "Jar überschreiben" wurde schon zig fach besprochen. Ich hätte hierfür aber noch ein paar Fragen.

Wenn ich meine Java-Anwendung starte, dann liegen doch die Klassen entpackt und komplett in der VM oder sehe ich das falsch?

Ich habe einen Updatemechanismus in meiner Anwendung, der Jars von einem Server lädt. Die alten Jar´s meiner Anwendung werden aber dann auch zur Laufzeit meiner Software mit den neueren überschrieben, was ja rein von der Kopiermethode funktioniert. Sobald der User die Anwendung neu startet werden eben die neuen Jar´s und die darin enthaltenen Klassen verwendet.

Ich bin mir jetzt nicht mehr ganz sicher, ob dieses Vorgehen nicht irgendwann zu Problemen führt, wenn z.B. (was ich aber jetzt bewusst nicht mache) Klassen nachgeladen werden.

Viele Grüße
Sascha
 
Die VM lädt Klassen, die von deiner Anwendung benötigt werden, zur Laufzeit dynamisch über einen ClassLoader. Es werden also nicht alle Klassen am Anfang geladen. Die JARs werden auch nicht entpackt, sondern der ClassLoader sucht die .class-Dateien im ClassPath und entpackt sie im Falle eines JARs aus dieser Container-Datei (JARs sind ja auch nur ZIP-Dateien).
(( Die Aussagen sollten auf die meisten VMs zutreffen, das genaue Verhalten von VMs ist nicht so strikt definiert - Fakt ist aber, dass Klassen-Definitionen nicht nachgeladen werden dürfen ))
Sobald eine Klassendefinition geladen ist, ist es dem ClassLoader egal, ob sich deren .class-Datei geändert hat. Das ist sowohl bei JARs als auch bei Ordnern im ClassPath der Fall. Erst bei einem Neustart der VM wird die Klassen-Definition neu geladen. Dein Update-Mechanismus solle also funktionieren, wenn du die Anwendung immer nach einem Update neu startest.
Wenn du ein Update auch ohne kompletten Neustart ermöglichen möchtest, musst du mit ClassLoadern arbeiten, oder du nimmst eine Laufzeitumgebung wie OSGi, die das schon für dich übernimmt. Wegen einem einfachen Update-Mechanismus würde ich aber keine ClassLoader-Magie anwenden oder OSGi einsetzen. Es sei denn natürlich, du praktiziert Continuous Delivery und machst alle 15min ein Update ;-)
 
Hallo,

Ich habe einen Updatemechanismus in meiner Anwendung, der Jars von einem Server lädt. Die alten Jar´s meiner Anwendung werden aber dann auch zur Laufzeit meiner Software mit den neueren überschrieben, was ja rein von der Kopiermethode funktioniert. Sobald der User die Anwendung neu startet werden eben die neuen Jar´s und die darin enthaltenen Klassen verwendet.

Ich bin mir jetzt nicht mehr ganz sicher, ob dieses Vorgehen nicht irgendwann zu Problemen führt, wenn z.B. (was ich aber jetzt bewusst nicht mache) Klassen nachgeladen werden.
Ich würde dir empfehlen, deine Jars mit einer entsprechenden Versions-Nr im Dateinamen auszustatten und diese dann über einen programmatischen Selektionsmechanismus (siehe unten) dynamisch in den Classpath zu legen. Weiterhin solltest du deine Anwendung nach einem update automatisch auf Konsistenz (Übertragungsfehler, falsche Versionen) prüfen und die alten jar-Dateien behalten . So kannst du deine Anwendung beispielsweise auch bei fehlgeschlagenem Backup in einer alten Version starten.


(( Die Aussagen sollten auf die meisten VMs zutreffen, das genaue Verhalten von VMs ist nicht so strikt definiert - Fakt ist aber, dass Klassen-Definitionen nicht nachgeladen werden dürfen ))
Sobald eine Klassendefinition geladen ist, ist es dem ClassLoader egal, ob sich deren .class-Datei geändert hat. Das ist sowohl bei JARs als auch bei Ordnern im ClassPath der Fall. Erst bei einem Neustart der VM wird die Klassen-Definition neu geladen.
Ja aber wenn der ClassLoader, der die Klassen geladen hat (und zwar genau der), gc'ed wird können die Klassen auch wieder (neu) von der entsprechenden Quelle geladen werden.

Es gibt natürlich auch die Möglichkeit hier immer wieder einen neuen URLClassLoader zu erzeugen. Der "neue" URLClassLoader würde dann auch Änderungen an jars / class files mitbekommen (sofern diese nur über den UCL geladen wurden). Man darf aber dann auch nur noch mit dem neuen UCL arbeiten.

Wegen einem einfachen Update-Mechanismus würde ich aber keine ClassLoader-Magie anwenden oder OSGi einsetzen.
Eben, denn hier wäre dann beispielsweise im Falle von OSGi noch eine entsprechende Update Site aufzusetzen und ein Anwendungsupdate (z.Bsp. über P2) zu definieren. All dies kann eine Anwendung ganz schön aufblähen.

Wegen einem einfachen Update-Mechanismus würde ich aber keine ClassLoader-Magie anwenden oder OSGi einsetzen.
Dem schließe ich mich an :)

Eine einfache Alternative zu der zuvorgenannten Lösung auf Basis von OSGi wäre einfach die Verwendung des ServiceLoaders-APIs mit ein wenig zusätzlicher Versionsverwaltungslogik (Z.bsp. nimm immer das jar mit der höchsten Versionsnr, wobei die Versionnr dann an dem entsprechenden jar kodiert sein könnte: z.Bsp.: de.tutorials.app.server-2.1.332112.jar)

Natürlich bietet der Ansatz mit dem SevriceLoader nicht die umfassenden Möglichkeiten einer OSGi basierten Anwendung - stellt aber IMHO seine sehr einfache sowie pragmatische Lösung dar.

Siehe auch:
http://www.tutorials.de/java/358931-services-dynamisch-laden.html
http://www.tutorials.de/java/377348-einfacher-plugin-mechanismus-mit-dem-serviceloader-api.html
http://www.tutorials.de/java/310207-eine-art-plugin-system-2.html
http://www.tutorials.de/java/357126-wieder-mal-java-und-plug-ins.html

Gruß Tom
 
Um die Klassen neu zu laden muss nicht mal der alte ClassLoader gc'ed werden - du kannst einen neuen erzeugen, der auf dem Extension ClassLoader aufsetzt und deinen System-Classpath ClassLoader damit umgeht. Wenn ein ClassLoader nach den .class-Dateien sucht fängt er ja ganz unten in der Hierarchie an und du stellst damit sicher, dass deine Classpath-Dateien nicht genutzt werden.
Dein ClassLoader wird übrigens erst gc'ed, wenn alle Referenzen darauf weg sind, also auch alle Instanzen, die über .getClass() noch eine Referenz auf die Klasse haben. Das sicherzustellen ist eine schöne Aufgabe in Umgebungen mit multiplen ClassLoadern ;-)
Nicht zu vergessen das Problem, dass Instanzen der gleichen Klasse von verschiedenen ClassLoadern nicht Zuweisungskompatibel zueinander sind.
Für einen Update-Mechanismus ist das recht viel Detail-Kram und Fehlerpotential.

Das Update lässt sich über die OSGi-Mechanismen eigentlich recht unkompliziert realisieren. Die JARs liegen ja scheinbar schon irgendwo auf einem Server, das ist ja quasi eine Update-Site (wenn auch keine nach P2).
Auf der Basis einfach: Bundle BundleContext#install(String) - der String ist die location, also die URI. Das Bundle kann man dann starten, stoppen, updaten oder deinstallieren. P2 ist ja nur ein zusätzlicher Layer oben drüber, der einen Update-Mechanismus basierend auf Metadaten und Repository-Vorgaben (UpdateSite) realisiert. Kann man nutzen, muss man aber nicht. Wer sich schon mal mit P2 beschäftigen musste weiß auch, dass man es besser lässt, wenn man es nicht wirklich, wirklich, wirklich braucht :D

Den Ansatz mit der ServiceLoader-API verstehe ich nicht so ganz. AFAIU ist der Vorschlag, für jedes JAR einen neuen ClassLoader basierend auf dem vorherigen aufsetzten und dann beim Erstellen des ServiceLoaders den letzten ClassLoader als Parameter angeben. So weit so einfach.
Wenn der ServiceLoader aber Klassen lädt, benutzt er deren Signatur, also den FQDN. Wenn der sich nicht ändert, lädt er die Klasse nur aus dem erstbesten ClassLoader von der Wurzel der Hierarchie aus. Und das ist dann wieder die erstgeladene Klasse.
Bei dem Ansatz über ServiceLoader müsste man also auch die Implementierungen umbenennen und z.B. mit Versionsnummern versehen.
Wenn man die ServiceLoader so verwenden will, dass man die Klassennamen nicht ändern muss, ist man wieder bei unabhängigen ClassLoadern analog OSGi (oder eben basieren auf dem Extension ClassLoader).
Wenn ich das falsch verstehe, bitte ich um Erleuchtung :)
 
Hallo,

Um die Klassen neu zu laden muss nicht mal der alte ClassLoader gc'ed werden - du kannst einen neuen erzeugen, der auf dem Extension ClassLoader aufsetzt und deinen System-Classpath ClassLoader damit umgeht.
Das habe ich ja auch nicht anders beschrieben ;-)

ein ClassLoader wird übrigens erst gc'ed, wenn alle Referenzen darauf weg sind, also auch alle Instanzen, die über .getClass() noch eine Referenz auf die Klasse haben. Das sicherzustellen ist eine schöne Aufgabe in Umgebungen mit multiplen ClassLoadern
Klar - wenn man den neuen UCL mit einem anderen parent erzeugt ist das nicht schwer - wobei das auch nicht unbedingt notwedig ist.

nicht zu vergessen das Problem, dass Instanzen der gleichen Klasse von verschiedenen ClassLoadern nicht Zuweisungskompatibel zueinander sind.
Für einen Update-Mechanismus ist das recht viel Detail-Kram und Fehlerpotential.
Jo.

Den Ansatz mit der ServiceLoader-API verstehe ich nicht so ganz. AFAIU ist der Vorschlag, für jedes JAR einen neuen ClassLoader basierend auf dem vorherigen aufsetzten und dann beim Erstellen des ServiceLoaders den letzten ClassLoader als Parameter angeben. So weit so einfach.
Nicht ganz, aber so habe ich das auch nicht beschrieben ;-) Auch bei meinem vorgeschlagenen Ansatz muss man die Anwendung neustarten. Ich habe vorgeschlagen den URLClasspath, sprich die Selektion der zu ladenen jars über eine entsprechende Versionsvergleichslogik dynamisch beim Bootstrap der Anwendung vorzunehmen. Dies habe ich angedeutet mit: "(Z.bsp. nimm immer das jar mit der höchsten Versionsnr, wobei die Versionnr dann an dem entsprechenden jar kodiert sein könnte: z.Bsp.: de.tutorials.app.server-2.1.332112.jar)
". Sprich, wenn der Client eine neue Version eines oder mehrer jars findet lädt er diese herunter und verlangt einen neustart. Anschließend merkt der Client, dass er eine neue Version eines oder auch mehrer jars hat und bindet nun die neueren Versionen in den Classpath des UCL ein.

Bei dem Ansatz über ServiceLoader müsste man also auch die Implementierungen umbenennen und z.B. mit Versionsnummern versehen.
Wenn man die ServiceLoader so verwenden will, dass man die Klassennamen nicht ändern muss, ist man wieder bei unabhängigen ClassLoadern analog OSGi (oder eben basieren auf dem Extension ClassLoader).
... diese Problematik tritt deshalb nicht auf, da nach dem oben skizzierten Ansatz immer nur eine Version der Klasse geladen wird (aus dem jar mit der "höchsten" Versionsnummer).

Schau mal hier:
http://www.tutorials.de/java/358931-services-dynamisch-laden.html
Wenn man dort innerhalb von registerPlugins bzw. um addToClasspath noch eine Jar-Selektionslogik à la "wenn du app-1.1.1111.jar und app-1.2.2233.jar siehst nimm die höchste Version -> app-1.2.2233.jar" einbaut, dann hat man IMHO schon so ziemlich alles was man braucht.

Natürlich muss man hier gewährleisten, dass die jars updates auch Kompatibel zueinander sind. Deshalb bietet es sich hier an - ähnlich wie bei OSGi - Update / Feature-Packs zu schüren und diese dann immer gemeinsam als update zu laden. So bewegt man die Anwendung von einem konsistenten Zustand in einen neuen.

Btw. @ MS Tech hast du schon mal darüber nachgedacht Java Webstart zu verwenden?
http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136112.html

Bei Java Webstart hättest du eine Update Funktion schon von "Haus aus".

Gruß Tom
 
UCL = URLClassLoader? War mir nicht geläufig. TLAs ...

---Zitat---
Klar - wenn man den neuen UCL mit einem anderen parent erzeugt ist das nicht schwer - wobei das auch nicht unbedingt notwedig ist.
---Zitatende---
Das interessiert mich - ich hatte mal das Problem, dass Referenzen eines ClassLoaders in einen fremden Kontext übergeben wurden und die GC des ClassLoaders verhindert haben. Die Zuweisung in den fremden Kontext erfolgte über den Typ 'Object' und war daher unproblematisch. Wie soll das über einen anderen parent verhindert werden? Bootstrap ist doch die Wurzel von allem, oder irre ich?
 
Hallo Zusammen,

danke für eure Anworten. Ich muss mir jetzt erstmal "durchlesen" :).

Also was ich generell nicht will ist einen total aufgeblähten Updatemechanismus. Momentan ist es ja bereits möglich neue Jars bzw. eine neue Programmversion von meinem Server zu laden. Beim momentanen Stand werden eben die alten Jar´s mit den neuen überschrieben. Welche Jar´s runtergeladen werden müssen und welche nicht, entscheide ich bereits bei der auswahl der downloadbaren jars und das funktioniert auch soweit.

Ich muss ja eigentlich sogar einen Neustart bzw. ein Schließen der Anwendung durchführen, da bei manchen Updates auch die Datenbank an das Datenmodell angepasst werden muss. Ich denke das Fehlerpotential würde enorm steigen, wenn ich das während der Laufzeit machen würde, speziell in einer Client- und Serverumgebung.

Was mich generell abschrecken würde, wäre viel zusätzlicher Verwaltungsaufwand mit diesen Updates.

Viele Grüße
Sascha
 
Hmmm ... hatte gerade eine radikal einfache Idee.

Dein Mechanismus funktioniert soweit ja. Also Finger weg davon ;-)

Du hast aber recht - es könnten Probleme beim Nachladen von Klassen entstehen.

Mein Vorschlag für eine sichere Update-Prozedur:
  1. Neues JAR runterladen und in einem temporären Verzeichnis speichern.
  2. Das runtergeladene JAR nach allen Regeln der Kunst prüfen und validieren.
  3. Der VM-Runtime einen ShutdownHook anhängen, der das JAR der Anwendung überschreibt mit dem aus dem temporären Verzeichnis. Das ganze natürlich in einer ACID Transaktion (als ob ;-))
  4. Den Benutzer dazu auffordern, in aller Ruhe die Anwendung runter zu fahren, damit das Update durchgeführt werden kann.

Runtime.html#addShutdownHook(java.lang.Thread)

Der ShutdownHook sollte ganz am Ende der Ausführung der VM durchgeführt werden. Deine Anwendung sollte dann eigentlich keine Klassen mehr nachladen.

Das ganze ist so radikal einfach, weil du einfach nur den einen Befehl für das Kopieren des JARs in einer neuen Thread-Instanz kapseln musst. Und die Thread-Instanz musst du dann bei der Runtime registrieren. Das sind ... 5 Zeilen zusätzlicher Code, wenn du die schließenden curly brackets mit zählst.
 
Zurück