Ein Klassenlader ist dafür verantwortlich, die Binärrepräsentation einer Klasse aus einem Hintergrundspeicher oder Hauptspeicher zu laden. Aus der Datenquelle (im Allgemeinen die .class-Datei) liefert der Klassenlader ein Byte-Array mit den Informationen, die im zweiten Schritt dazu verwendet werden, die Klasse ins Laufzeitsystem einzubringen; das ist Linking. Es gibt vordefinierte Klassenlader und die Möglichkeit, eigene Klassenlader zu schreiben, um etwa verschlüsselte vom Netzwerk zu beziehen oder komprimierte .class-Dateien aus Datenbanken zu laden.
Klassenladen auf Abruf
Nehmen wir zu Beginn ein einfaches Programm mit drei Klassen:
package com.tutego.insel.tool;
public class HambachForest {
public static void main( String[] args ) {
boolean rweWantsToCutTrees = true;
Forrest hambachForest = new Forrest();
if ( rweWantsToCutTrees ) {
Protest<Forrest> p1 = new Protest<>();
p1.believeIn = hambachForest;
}
}
}
class Forrest { }
class Protest<T> {
T believeIn;
java.time.LocalDate since;
}
Wenn die Laufzeitumgebung das Programm HambachForest startet, muss sie eine Reihe von Klassen laden. Das tut sie dynamisch zur Laufzeit. Sofort wird klar, dass es zumindest HambachForest sein muss. Und da die JVM die statische main(String[])-Methode aufruft und Optionen übergibt, muss auch String geladen sein. Unsichtbar stecken noch andere referenzierte Klassen dahinter, die nicht direkt sichtbar sind. So wird zum Beispiel Object geladen, da implizit in der Klassendeklaration von HambachForest steht: class HambachForest extends Object. Intern ziehen die Typen viele weitere Typen nach sich. String implementiert Serializable, CharSequence und Comparable, also müssen diese drei Schnittstellen auch geladen werden. Und so geht das weiter, je nachdem, welche Programmpfade abgelaufen werden. Wichtig ist aber, zu verstehen, dass diese Klassendateien so spät wie möglich geladen werden.
Klassenlader bei der Arbeit zusehen
Im Beispiel lädt die Laufzeitumgebung selbstständig die Klassen (implizites Klassenladen). Klassen lassen sich auch mit Class.forName(String) über ihren Namen laden (explizites Klassenladen).
Um zu sehen, welche Klassen überhaupt geladen werden, lässt sich der virtuellen Maschine beim Start der Laufzeitumgebung ein Schalter mitgeben: -verbose:class. Dann gibt die Maschine beim Lauf alle Typen aus, die sie lädt. Nehmen wir das Beispiel von eben, so ist die Ausgabe mit dem aktivierten Schalter unter Java 11 fast 500 Zeilen lang; ein Ausschnitt:
$ java -verbose:class com.tutego.insel.tool.HambachForest
[0.010s][info][class,load] opened: C:\Program Files\Java\jdk-11\lib\modules
[0.032s][info][class,load] java.lang.Object source: jrt:/java.base
[0.032s][info][class,load] java.io.Serializable source: jrt:/java.base
[0.033s][info][class,load] java.lang.Comparable source: jrt:/java.base
[0.036s][info][class,load] java.lang.CharSequence source: jrt:/java.base
[0.037s][info][class,load] java.lang.String source: jrt:/java.base
…
[0.684s][info][class,load] sun.security.util.Debug source: jrt:/java.base
[0.685s][info][class,load] com.tutego.insel.tool.HambachForest source: file:/C:/Inselprogramme/target/classes/
[0.687s][info][class,load] java.lang.PublicMethods$MethodList source: jrt:/java.base
[0.687s][info][class,load] java.lang.PublicMethods$Key source: jrt:/java.base
[0.689s][info][class,load] java.lang.Void source: jrt:/java.base
[0.690s][info][class,load] com.tutego.insel.tool.Forrest source: file:/C:/Inselprogramme/target/classes/
[0.691s][info][class,load] jdk.internal.misc.TerminatingThreadLocal$1 source: jrt:/java.base
[0.692s][info][class,load] java.lang.Shutdown source: jrt:/java.base
[0.692s][info][class,load] java.lang.Shutdown$Lock source: jrt:/java.base
Ändern wir die Variable rweWantsToCutTrees auf true, so wird unsere Klasse Protest geladen, und in der Ausgabe kommt nur eine Zeile hinzu! Das wundert auf den ersten Blick, denn die Klasse referenziert LocalDate. Doch ein LocalDate wird nicht benötigt, also auch nicht geladen. Der Klassenlader bezieht nur Klassen, wenn die für den Programmablauf benötigt werden, nicht aber durch die reine Deklaration als Attribut. Wenn wir LocalDate mit zum Beispiel LocalDate.now() initialisieren kommen stattliche 200 Klassendateien hinzu.