Archive for the ‘Classloader Chirurgie’ Category

Kurze Einführung in ClassLoader – Teil 4

Monday, June 18th, 2018

Der Servlet Container Apache Tomcat ist die Referenzimplementierung der Servlet- und JSP-API. In ihm laufen Webapplikationen, die zur Laufzeit installiert (”deployed”) und deinstalliert (”undeployed”) werden können. Damit das funktioniert, muss Tomcat für jede Webapplikation einen eigenen ClassLoader zur Verfügung stellen. Zudem kann es Klassen geben, die von mehreren Webapplikationen gemeinsam verwendet werden.

Tomcat löst diese Anforderungen durch folgende ClassLoader Hierarchie:

ClassLoader Hierarchie des Apache Tomcats.

Common:

Alle Klassen im /common Verzeichnis des Servers sind sowohl in den Webapplikationen und für Tomcat selbst sichtbar. Hier werden üblicherweise Datenbank-Treiber abgelegt. Entwickler sollten hier keine eigenen JAR-Dateien platzieren, um eventuelle Kompatibilitätskonflikte mit Tomcat zu vermeiden.

Shared:

Die im /shared Verzeichnis befindlichen Klassen stehen allen Webapplikationen zur Verfügung. Sie müssen diese Klassen dann nicht mehr selbst mitbringen. Ein weitere Anwendungszweck ist die Kommunikation zwischen Webapplikationen oder der Einsatz von Singletons, die von mehreren Webapplikationen benötigt werden.

Server:

Die vom Tomcat Server benötigten Klassen befinden sich im /server Verzeichnis. Die Webapplikationen haben keinen Zugriff darauf.

WebApp:

Jede Webapplikation besitzt einen eigenen ClassLoader, der unterhalb vom Shared ClassLoader liegt. Interessant ist, dass hier der Delegationsmechanismus umgekehrt wird:

It is recommended also that the application class loader be implemented so
that classes and resources packaged within the WAR are loaded in preference to
classes and resources residing in container-wide library JARs.

SRV 9.7.2

Auf gut deutsch: Der ClassLoader der Webapplikation versucht zuerst die Klasse zu laden und erst dann wird delegiert. Somit können Webapplikationen Klassen vom Shared oder Common ClassLoader “überschreiben” (z.B. um zu garantieren, dass genau eine bestimmte Version einer Library zum Einsatz kommt).

:)

Mehr zum Thema ClassLoader im Tomcat gibts direkt auf der Tomcat-Homepage. Soweit so gut. Mit diesem Teil ist die ClassLoader – Reihe vorerst abgeschlossen

P.S.: Noch ein Blogtipp von mir: Auf http://leo.freeflux.net/blog gibts ebenfalls interessante Javaartikel.

Kurze Einführung in ClassLoader – Teil 3

Monday, June 11th, 2018

Ein eigener ClassLoader muss von java.lang.ClassLoader ableiten und sollte die findClass-Methode überschreiben. Diese Methode benötigt den vollständigen Klassennamen und gibt die geladene Klasse in Form eines Class Objektes zurück.

Der nachfolgende ClassLoader MyClassLoader lädt die Klassen aus einem übergebenen Dateipfad. Wie in Java üblich, wird die Packagestruktur auf das Filesystem übertragen (d.h. die Klasse ch.javablog.reloadable.MyTest liegt unter ../ch/javablog/reloadable).

Im Konstruktor von MyClassLoader wird der “parent class loader” übergeben, damit der Delegationsmechanismus funktionieren kann. Erst wenn der “parent class loader” die zu ladende Klasse nicht finden konnte, wird unsere findClass-Methode aufgerufen. Dort lesen wir das “.class”-File ein und erzeugen ein byte-Feld (Zeile 23-29).

Dieses byte-Feld wird für die Methode defineClass der Oberklasse benötigt, denn sie macht die eigentliche Arbeit und generiert aus dem byte-Feld eine Java Klasse

ch.javablog.MyClassLoader:

  1. public class MyClassLoader extends ClassLoader {
  2.     private final String startPath;
  3.     private final String fileSeparator = System.getProperty(“file.separator”);
  4.     public MyClassLoader(ClassLoader parent, String startPath) {
  5.         super(parent);
  6.         this.startPath = startPath;
  7.     }
  8.  
  9.     protected Class findClass(String name) throws ClassNotFoundException {
  10.         // Absoluten Pfad zur Klasse finden
  11.         StringBuffer path       = new StringBuffer(this.startPath);
  12.         String[] splittedName   = name.split(\\\\.”);
  13.         List splittedNameList   = Arrays.asList(splittedName);
  14.  
  15.         Iterator it = splittedNameList.iterator();
  16.         while (it.hasNext()) {
  17.             String pathFragment = (String) it.next();
  18.             path.append(fileSeparator + pathFragment);
  19.         }
  20.         path.append(“.class”);
  21.         System.out.println(“MyClassLoader – loading: “ + path.toString());
  22.         // Klasse aus Datei laden
  23.         File classFile = new File(path.toString());
  24.         int fileLength = (int) classFile.length();
  25.         byte[] fileContent = new byte[fileLength];
  26.         FileInputStream fis = null;
  27.         try {
  28.             fis = new FileInputStream(classFile);
  29.             fis.read(fileContent);
  30.  
  31.             // Erzeugt aus dem byte Feld ein Class Object.
  32.             return super.defineClass(name, fileContent, 0, fileLength);
  33.         } catch (Exception e) {
  34.             throw new ClassNotFoundException(e.toString());
  35.         } finally {
  36.             if (fis != null) {
  37.                 try {
  38.                     fis.close();
  39.                 } catch (Exception ignored) {
  40.  
  41.                 }
  42.             }
  43.         }
  44.     }
  45. }

In einer kleinen Main-Methode probieren wir MyClassLoader aus. Wichtig: Die Klasse ch.javablog.reloadable.MyTest befindet sich nicht im CLASSPATH, sondern in dem Verzeichnis “myclasses” (parallel zum “classes” Verzeichnis).

classes und myclasses Verzeichnis liegen auf der selben Ebene

ch.javablog.Main :

  1. public static void main(String[] args) throws Exception{
  2.         ClassLoader clCurrent = Thread.currentThread().getContextClassLoader();
  3.         ClassLoader clNew = new MyClassLoader(clCurrent, “.” + fileSeparator + “myclasses”);
  4.         Class myTest = clNew.loadClass(“ch.javablog.reloadable.MyTest”);
  5.         System.out.println(myTest + ” classloader: “ + myTest.getClassLoader());
  6.     }

Ausgabe:

MyClassLoader - loading: .\myclasses\ch\javablog\reloadable\MyTest.class<br /> class ch.javablog.reloadable.MyTest classloader: ch.javablog.MyClassLoader@923e30

Interpretation:

Die MyTest wurde erfolgreich von MyClassLoader geladen. Wenn MyTest weitere Klassen importiert, werden sie ebenfalls von MyClassLoader geladen. Die untere Abbildung zeigt die Position unseres ClassLoaders in der ClassLoader-Hierarchie.

ClassLoader Struktur Teil 3

Im nächsten Teil geht es um die ClassLoader im Apache Tomcat, und warum er einen anderen Delegationsmechanismus einsetzt.

Kurze Einführung in ClassLoader – Teil 2

Tuesday, June 5th, 2018

Wenn ein Class Loader eine Klasse laden muss, delegiert er die Anfrage zunächst an seinen “parent class loader”. Erst wenn dieser die gewünschte Klasse nicht finden konnte, probiert er es selber die Klasse zu laden.
Das testen wir doch gerade mal. Wir schreiben zwei Klassen: Customer und Main. Beide Klassen befinden sich im CLASSPATH. In der main-Methode der Main-Klasse werden folgende Objekte geholt:

  • ClassLoader des aktuellen Threads
  • ClassLoader von java.lang.String
  • ClassLoader von ch.javablog.Main
  • ClassLoader von ch.javablog.Customer

Alle diese Objekte geben wir auf der Kommandozeile aus.

Beispielcode:

public static void main(String[] args) {
ClassLoader clCurrent = Thread.currentThread().getContextClassLoader();
System.out.println("current context classloader: " + clCurrent);
 
ClassLoader clString = String.class.getClassLoader();
System.out.println("java.lang.String classloader: " + clString);
 
ClassLoader clCustomer = Customer.class.getClassLoader();
System.out.println("ch.javablog.Customer classloader: " + clCustomer);
 
ClassLoader clMain = Main.class.getClassLoader();
System.out.println("ch.javablog.Main classloader: " + clMain);
    }

Ausgabe auf der Kommandozeile:

current context classloader: sun.misc.Launcher$AppClassLoader@133056f
java.lang.String classloader: null
ch.javablog.Customer classloader: sun.misc.Launcher$AppClassLoader@133056f
ch.javablog.Main classloader: sun.misc.Launcher$AppClassLoader@133056f

Interpretation:
Der ClassLoader des Threads ist der System ClassLoader (sun.misc.Launcher). Er lädt die String-Klasse nicht selber, sondern delegiert das Laden der String-Klasse an seinen “parent class loader”, dem Bootstrap ClassLoader. Dieser wird nicht als ClassLoader-Objekt repräsentiert, stattdessen wird beim Zugriffsversuch null zurückgegeben.

Die Klassen Customer und Main werden vom System ClassLoader auch an den Bootstrap ClassLoader delegiert. Der kennt sie aber nicht und von daher muss der System ClassLoader diese Klassen selber laden.

ClassLoader Struktur

Bis jetzt alles noch sehr einfach. Im nächsten Teil schreiben wir einen eigenen ClassLoader…

Kurze Einführung in ClassLoader – Teil 1

Monday, June 4th, 2018

Jeder kennt Sie, jeder hasst sie: ClassCastException, ClassNotFoundException und NoClassDefFoundError.

Das Laden von Klassen durch ClassLoader erlaubt zwar wunderbare Dinge wie “Hot Deployment” (Dynamisches Austauschen von Klassen zur Laufzeit), sorgt aber auch für eine Reihe von potentiellen Stolpersteinen. Leider stolpert man gerade dann drüber, wenn man überhaupt keine Zeit für Ursachenforschung hat ;). Dewegen gibts hier eine kleine ClassLoader-Einführung.

Eins vorweg: Es gibt zwei Typen von ClassLoader:

There are two types of class loaders: user-defined class loaders and the bootstrap class loader supplied by the Java virtual machine. Every user-defined class loader is an instance of a subclass of the abstract class ClassLoader.

JLS (5.3)

ClassLoader sind hierarchisch aufgebaut. D.h. jeder besitzt genau einen “parent class loader”. Bis auf den “bootstrap class loader”. Denn er steht an der obersten Stelle der ClassLoader-Hierarchie und ist für das Laden der Java API (z.B. String oder Integer) zust�ndig (Konkret lädt er die Klassen aus rt.jar). Unter ihm liegt der erste “user-defined class loader”: Der System ClassLoader. Er lädt die Klassen, die im CLASSPATH angegeben werden.

Das nachfolgende Bild zeigt die ClassLoader-Struktur im einfachsten Fall (keine Sorge, es wird noch komplexer…)

So viel für heute. Im nächsten Teil gehen wir auf den Delegation Mechanismus ein…