Tiefes Verständnis des Ladevorgangs der JVM-Klasse

Dieser Artikel dient zum Lesen von Notizen. Ich persönlich denke, dass dieser Teil im Vergleich zur Speicherbereinigung sehr wichtig ist, daher möchte ich eine Notiz behalten

1. Der Lebenszyklus einer Klasse

Last -> Authentifizierung -> Vorbereitung -> Parse -> Initialisierung -> Anwendung -> Uninstall
Fügen Sie hier eine Bildbeschreibung ein
Punkte:

  • Die Reihenfolge der fünf Stufen des Ladens, Verifizierens, Vorbereitens, Initialisierens und Entladens wird bestimmt
  • Die Analysephase kann in einigen Fällen nach der Initialisierungsphase beginnen
  • Das tatsächliche "Laden" der ersten Stufe des Klassenladeprozesses ist nicht klar definiert
  • In sechs Fällen muss die Klasse jedoch sofort "initialisiert" werden:
    1. Wenn Sie auf die vier Bytecode-Anweisungen new, getstatic, putstatic oder invokestatic stoßen und der Typ nicht initialisiert wurde, müssen Sie zuerst die Initialisierungsphase auslösen.

Die Auslösebedingungen dieser vier Anweisungen sind:

  • Wenn Sie das neue Schlüsselwort zum Instanziieren eines Objekts verwenden.
  • Lesen oder legen Sie eine Art statisches Feld fest
  • Beim Aufruf einer statischen Methode einer Klasse

2. Reflect-Aufruf, Initialisierung, wenn nicht initialisiert.
3. Hauptklasse für die Ausführung der Hauptmethode.
4. Reflect-Methodenhandle wird in REF_getStatic, REF_putStatic, REF_invokeStatic, REF_newInvokeSpecial aufgelöst. Die entsprechende Klasse wurde nicht initialisiert.
5. Die Schnittstellenmethode wurde durch das Standardschlüsselwort geändert Die implementierte Klasse wird zuerst initialisiert
. 6. Wenn die Klasse initialisiert wird und die übergeordnete Klasse nicht initialisiert wird, wird die Initialisierung der übergeordneten Klasse ausgelöst.
Nur diese sechs Szenarien repräsentieren aktive Verweise auf einen Typ

Implementierungsfall:

  • Das Verweisen auf statische Felder, die in der übergeordneten Klasse über ihre Unterklassen definiert sind, löst nur die Initialisierung der übergeordneten Klasse und nicht der Unterklassen aus.
  • Konstanten werden zur Kompilierungszeit zum Konstantenpool der Klasse hinzugefügt. Im Wesentlichen gibt es keinen direkten Verweis auf die Klasse, die die Konstante definiert, sodass keine Initialisierung ausgelöst wird
  • Das Array löst keine Initialisierung der Klasse aus. Der Grund dafür ist, dass dieser Typ einer virtuellen Maschine automatisch eine Unterklasse generiert, die direkt von java.lang.Object erbt. Die Erstellungsaktion wird durch den Bytecode-Befehl newarray ausgelöst.
    Wenn Sie überlegen, ob eine Klasse initialisiert wird, prüfen Sie, ob sie passiv referenziert wird, dh die Klasseninitialisierung ist verzögert.
    Hier müssen Materialien getestet werden. Ich liste sie auf und sie verursacht keine Initialisierung.
  • Klassenobjekt
  • LoadClass-Methode zum Laden von Klassen
  • Wenn der zweite Parameter von Class.forname () false ist
  • Die Schnittstelle verfügt auch über einen Initialisierungsprozess, der fast dem der Klasse entspricht

Persönliches Verständnis der Klasseninitialisierung:
Tatsächlich wurde oben erwähnt, dass die Klasseninitialisierung faul ist, was Speicherplatz sparen kann. Natürlich betonen einige Programme einen Vorheizprozess. Wenn wir alles am Anfang vorheizen, wird es sein Keine Notwendigkeit zu initialisieren

Beispiel:
Fügen Sie hier eine Bildbeschreibung ein
Test 1:
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein
Test 2:
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein
Wie kann ich die Initialisierung von MM auslösen?
Fügen Sie hier eine Bildbeschreibung ein
Führen Sie dann den obigen Code aus:
Fügen Sie hier eine Bildbeschreibung ein
Dies führt zu einer Schlussfolgerung: Nur Basistypen und Zeichenfolgen können als direkte Verweise auf ihren eigenen konstanten Pool verwendet werden.
Andere Referenztypen können als endgültige Klassen verwendet werden. Die Initialisierung dieser Klasse wird ausgelöst, dh <cinit>, und die Hauptaufgabe von <cinit> besteht darin, die in der Klasse definierten Elementvariablen zu initialisieren.

2. Der Prozess des Ladens der Klasse

2.1 Ladephase

Bitte haben Sie Verständnis dafür, dass die Phase "Laden" eine Phase des gesamten Prozesses "Laden von Klassen" ist

1) Erhalten Sie den binären Bytestream, der diese Klasse durch ihren vollständig qualifizierten Namen definiert.
2) Konvertieren Sie die durch diesen Bytestrom dargestellte statische Speicherstruktur in die Laufzeitdatenstruktur des Methodenbereichs. 3) Generieren Sie ein java.lang.Class-Objekt, das diese Klasse im Speicher als Zugriffspunkt auf verschiedene Daten dieser Klasse im Methodenbereich darstellt .

Hinweis:

  • Die Array-Klasse selbst wird nicht vom Klassenladeprogramm erstellt, aber der Elementtyp der Array-Klasse wird weiterhin vom Klassenladeprogramm geladen
  • Die Ladephase ist noch nicht abgeschlossen, die Verbindungsphase hat möglicherweise begonnen

2.2 Überprüfungsphase

Damit soll sichergestellt werden, dass die im Byte-Stream der Klassendatei enthaltenen Informationen alle Einschränkungen der "Java Virtual Machine Specification" erfüllen, der Garantiestufe für die Sicherheit der Java-Sprache

1. Überprüfung des Dateiformats

  • Ob es mit der magischen Zahl 0xCAFEBABE beginnt.
  • Gibt an, ob die Haupt- und Nebenversionsnummern innerhalb des von der aktuellen virtuellen Java-Maschine akzeptierten Bereichs liegen.
  • Gibt es nicht unterstützte Konstantentypen in den Konstanten des Konstantenpools (überprüfen Sie das Konstanten-Tag-Flag).
  • Gibt es eine Konstante, die auf eine nicht vorhandene Konstante verweist, oder stimmt sie nicht mit dem Typ in verschiedenen Indexwerten überein, die auf eine Konstante verweisen?
  • Enthält die Konstante CONSTANT_Utf8_info Daten, die nicht der UTF-8-Codierung entsprechen?
  • Gibt an, ob in jedem Teil der Klassendatei und in der Datei selbst gelöschte oder zusätzliche andere Informationen enthalten sind.
  • ... warte

2. Metadatenüberprüfung (semantische Überprüfung von Klassenmetadateninformationen)

  • Hat diese Klasse eine übergeordnete Klasse (außer java.lang.Object sollten alle Klassen eine übergeordnete Klasse haben).
  • Gibt an, ob die übergeordnete Klasse dieser Klasse eine Klasse erbt, die nicht vererbt werden darf (eine von final geänderte Klasse).
    Wenn die Klasse keine abstrakte Klasse ist, werden alle Methoden implementiert, die für die übergeordnete Klasse oder die Schnittstelle erforderlich sind.
  • Ob die Felder und Methoden in der Klasse mit der übergeordneten Klasse in Konflikt stehen (z. B. das Überschreiben des letzten Felds der übergeordneten Klasse oder eine Methodenüberladung, die nicht den Regeln entspricht, z. B. die Methodenparameter sind konsistent, aber der Rückgabewerttyp ist unterschiedlich usw.).
  • ... warte

3. Bytecode-Überprüfung

  • Stellen Sie sicher, dass der Datentyp und die Befehlscode-Sequenz des Operandenstapels jederzeit zusammenarbeiten können
  • Stellen Sie sicher, dass keine Sprunganweisung nicht zur Bytecode-Anweisung außerhalb des Methodenkörpers springt.
  • · Stellen Sie sicher, dass die Typkonvertierung im Methodenkörper immer gültig ist. Sie können beispielsweise dem Datentyp der übergeordneten Klasse ein Unterklassenobjekt zuweisen, das sicher ist, das übergeordnete Klassenobjekt jedoch dem Datentyp der Unterklasse zuweisen und das Objekt sogar zuweisen Ein Datentyp, der keine Vererbungsbeziehung hat und völlig irrelevant ist, ist gefährlich und illegal.
  • ... warte

4. Überprüfung der Symbolreferenz

  • Ob der vollständig qualifizierte Name, der durch die Zeichenfolge in der Symbolreferenz beschrieben wird, die entsprechende Klasse finden kann.
  • Gibt es Methoden und Felder, die durch den Felddeskriptor und den einfachen Namen der Methode in der angegebenen Klasse beschrieben werden?
  • Ob die aktuelle Klasse auf die Zugänglichkeit (privat, geschützt, öffentlich) der Klasse, des Felds und der Methode in der Symbolreferenz zugreifen kann.

2.3 Vorbereitungsphase

Die Vorbereitungsphase ist die Phase der offiziellen Zuweisung von Speicher für die in der Klasse definierten Variablen (dh statische Variablen, durch statische Änderungen modifizierte Variablen) und die Festlegung des Anfangswertes der Klassenvariablen

Hinweis: Die
Speicherzuweisung umfasst nur Klassenvariablen, keine Instanzvariablen. Instanzvariablen werden im Java-Heap zusammen mit dem Objekt zugewiesen, wenn das Objekt instanziiert wird.
Beispiele:

public static int value = 123;

Der Anfangswert des Variablenwerts nach der Vorbereitungsphase ist 0 anstelle von 123, da zu diesem Zeitpunkt noch keine Java-Methode ausgeführt wurde und die putstatische Anweisung mit dem 123 zugewiesenen Wert lautet, dass das Programm kompiliert und in der class constructor () -Methode gespeichert wird , so dass der Wert Initialisierungsphase der Aktion 123 Klasse zugeordnet werden ausgeführt.
Aber für:

public static final int value = 123;

Wenn das ConstantValue-Attribut in der Feldattributtabelle des Klassenfelds vorhanden ist, wird der Variablenwert während der Vorbereitungsphase auf den vom ConstantValue-Attribut angegebenen Anfangswert initialisiert

2.4 Auflösungsstufe

In der Auflösungsphase werden die Symbolreferenzen im Konstantenpool durch direkte Referenzen durch die Java Virtual Machine ersetzt

Zur Verdeutlichung des Konzepts:
Symbolreferenz: Die Symbolreferenz beschreibt das referenzierte Ziel mit einer Reihe von Symbolen. Das Symbol kann eine beliebige Form von Literal sein, sofern es verwendet werden kann, um das Ziel ohne Mehrdeutigkeit zu lokalisieren.

Direkte Referenz: Eine direkte Referenz ist ein Zeiger, der direkt auf das Ziel zeigen kann, ein relativer Versatz oder ein Handle, das indirekt auf das Ziel lokalisiert werden kann.
1. Klassen- oder Schnittstellenanalyse
2. Feldanalyse
3. Methodenanalyse

2.5 Initialisierungsphase

Erst in der Initialisierungsphase begann die virtuelle Java-Maschine tatsächlich mit der Ausführung des in der Klasse geschriebenen Java-Programmcodes und übergab das Steuerelement an die Anwendung.
In der Initialisierungsphase wird die Methode class constructor () ausgeführt.

Die Methode <clinit> () wird vom Compiler generiert und sammelt automatisch die Zuweisungsaktionen aller Klassenvariablen in der Klasse und die Anweisungen im statischen Anweisungsblock (statischer {} Block). Die Reihenfolge, in der der Compiler die Anweisungen sammelt, befindet sich in der Quelldatei Die Reihenfolge des Auftretens bestimmt, dass nur auf die vor dem statischen Anweisungsblock definierten Variablen im statischen Anweisungsblock zugegriffen werden kann und auf die nach seiner Zuweisung definierten Variablen, die jedoch im vorherigen statischen Anweisungsblock nicht zugegriffen werden können

Die Java Virtual Opportunity stellt sicher, dass die Methode <clinit> () der übergeordneten Klasse ausgeführt wurde, bevor die Methode <clinit> () der untergeordneten Klasse ausgeführt wird.

Die Methode <clinit> () ist für eine Klasse oder Schnittstelle nicht erforderlich. Wenn kein statischer Anweisungsblock oder keine Zuordnung von Variablen zu einer Klasse vorhanden ist, generiert der Compiler möglicherweise nicht die Methode <clinit> () für diese Klasse.
Die Methode <clinit> () der Ausführungsschnittstelle muss nicht zuerst die Methode <clinit> () der übergeordneten Schnittstelle ausführen, da die übergeordnete Schnittstelle nur initialisiert wird, wenn die in der übergeordneten Schnittstelle definierten Variablen verwendet werden.

Die Java Virtual Machine muss sicherstellen, dass die <clinit> () -Methode einer Klasse in einer Multithread-Umgebung ordnungsgemäß gesperrt und synchronisiert ist

3. Klassenlader

Das Designteam für Java Virtual Machine beabsichtigt, während der Ladephase der Klasse außerhalb der Java Virtual Machine die Aktion "Erhalten eines binären Bytestroms, der eine Klasse anhand ihres vollständig qualifizierten Namens beschreibt" durchzuführen, damit die Anwendung entscheiden kann, wie sie vorgehen soll. Holen Sie sich die gewünschte Klasse. Der Code, der diese Aktion implementiert, wird als "Class Loader" bezeichnet.

Für jede Klasse müssen der Klassenlader, der sie lädt, und die Klasse selbst gemeinsam ihre Eindeutigkeit in der virtuellen Java-Maschine feststellen . Jeder Klassenlader verfügt über einen unabhängigen Klassennamensraum.

Das heißt, ob zwei Klassen "gleich" sind, ist nur dann von Bedeutung, wenn die beiden Klassen von demselben Klassenlader geladen werden. Andernfalls sind sie gleich, selbst wenn die beiden Klassen aus derselben Klassendatei stammen Das Laden der virtuellen Maschine muss ungleich sein, solange der Klassenlader, der sie lädt, unterschiedlich ist.

Wenn ein Klassenobjekt von zwei Klassenladeprogrammen geladen wird und das entsprechende Objekt generiert wird, ist das Ergebnis falsch, wenn die Typprüfung des Objekts gehört

3.1 Übergeordnetes Delegierungsmodell

Kategorie:
Bootstrap Class Loader (BootstrapClassLoader), dieser Klassenlader ist in C ++ implementiert und Teil der virtuellen Maschine selbst;
alle anderen Klassenlader, diese Klassenlader, werden von der Java-Sprache implementiert und existieren unabhängig außerhalb der virtuellen Maschine Und alles von der abstrakten Klasse java.lang.ClassLoader geerbt.

Starten Sie den Klassenlader

Verantwortlich für das Laden und Speichern im Verzeichnis <JAVA_HOME> \ lib oder gespeichert im Pfad, der durch den Parameter -Xbootclasspath angegeben wird, und kann von der virtuellen Java-Maschine erkannt werden (identifiziert durch den Dateinamen wie rt.jar, tools.jar, der Name stimmt nicht überein Die Klassenbibliothek wird auch dann nicht geladen, wenn sie im lib-Verzeichnis abgelegt ist.) Die Klassenbibliothek wird in den Speicher der virtuellen Maschine geladen.
Der Startklassenlader kann vom Java-Programm nicht direkt referenziert werden. Wenn Sie beim Schreiben eines benutzerdefinierten Klassenladeprogramms die Ladeanforderung zur Verarbeitung an den Startklassenlader delegieren müssen, können Sie stattdessen direkt null verwenden.
Fügen Sie hier eine Bildbeschreibung ein

Lader für Erweiterungsklassen

Dieser Klassenlader ist in Form von Java-Code in der Klasse sun.misc.Launcher $ ExtClassLoader implementiert. Es ist dafür verantwortlich, alle Bibliotheken in das Verzeichnis <JAVA_HOME> \ lib \ ext oder in den von der Systemvariablen java.ext.dirs angegebenen Pfad zu laden.

Loader für Anwendungsklassen

Dieser Klassenlader wird von sun.misc.Launcher $ AppClassLoader implementiert. Da der Application Class Loader der Rückgabewert der Methode getSystem-ClassLoader () in der ClassLoader-Klasse ist, wird er in einigen Fällen auch als "System Class Loader" bezeichnet. Es ist für das Laden aller Klassenbibliotheken im Benutzerklassenpfad (ClassPath) verantwortlich, und Entwickler können diesen Klassenladeprogramm auch direkt im Code verwenden. Wenn Sie Ihren eigenen Klassenlader in der Anwendung nicht angepasst haben, ist dies im Allgemeinen der Standardklassenlader im Programm.

Übergeordnetes Delegierungsmodell

Fügen Sie hier eine Bildbeschreibung ein
Das übergeordnete Delegierungsmodell erfordert, dass der Rest des Klassenladeprogramms zusätzlich zum Startklassenladeprogramm der obersten Ebene über ein eigenes übergeordnetes Klassenladeprogramm verfügt.
Die Eltern-Kind-Beziehung zwischen Klassenladeprogrammen wird jedoch im Allgemeinen nicht in einer Vererbungsbeziehung (Vererbungsbeziehung) implementiert, sondern verwendet normalerweise eine Kompositionsbeziehung (Kompositionsbeziehung), um den Code des Elternladeprogramms wiederzuverwenden.

Arbeitsprozess:
Wenn ein Klassenlader lädt die Klasse eine Anfrage erhielt, ist es zunächst nicht selbst zu versuchen , diese Klasse zu laden , und dass diese Anforderung an den übergeordneten Klassenlader vollständig delegiert wird , jede Ebene des Class Loader ist wahr Daher sollten alle Ladeanforderungen schließlich an den Lader der Startklasse der obersten Ebene übertragen werden. Nur wenn der übergeordnete Lader meldet, dass er die Ladeanforderung nicht abschließen kann (die erforderliche Klasse wird in seinem Suchbereich nicht gefunden), wird der untergeordnete Lader Es wird versucht, das Laden von selbst abzuschließen.

Vorteile:
Ein offensichtlicher Vorteil besteht darin, dass Klassen in Java zusammen mit ihren Klassenladeprogrammen eine hierarchische Beziehung mit Priorität haben.

Beispielsweise wird die Klasse java.lang.Object, die in rt.jar gespeichert ist, unabhängig davon, welcher Klassenlader diese Klasse laden möchte, letztendlich an den Startklassenlader oben im Modell delegiert, um sie zu laden, sodass sich die Object-Klasse im Programm befindet In allen Arten von Klassenladeumgebungen kann dieselbe Klasse garantiert werden.

Wenn Sie es von anderen Klassenladern laden lassen, ist die endgültige Typbestimmung definitiv verwirrend

Code-Implementierung:
Fügen Sie hier eine Bildbeschreibung ein

Zerstören Sie die elterliche Delegation

loadclass-> findclass
JNDI- Dienst-> Der Basistyp muss auf den Benutzercode zurückgerufen werden.
Hot Deployment-> Wenn eine Klassenladeanforderung empfangen wird, scannen Sie die Klasse und laden Sie sie

Zusätzlich: Verschiedene Klassenlader laden dieselbe Klasse und schließlich wird getclass gleich, um den falschen Fall zu beurteilen

 public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myClassLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(filename);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }
        };

        Class<?> aClass = myClassLoader.loadClass("com.lyq.boot.lexicalanalysis.A");
        Object o =  aClass.newInstance();
        A a = new A();
        System.out.println(o.getClass().equals(a.getClass()));
    }

Kontextklassenlader

Das heißt, die vom übergeordneten Klassenladeprogramm geladene Klasse muss von der untergeordneten Klasse geladen werden. Der
untergeordnete Klassenladeprogramm behält die Referenz des übergeordneten Klassenladeprogramms bei. Was aber, wenn die vom übergeordneten Klassenladeprogramm geladene Klasse auf die vom untergeordneten Klassenladeprogramm geladene Klasse zugreifen muss? Das klassischste Szenario ist das Laden von JDBC.

JDBC ist eine Standardschnittstelle, die von Java für den Zugriff auf die Datenbank festgelegt wurde. Sie ist in der Java-Basisklassenbibliothek enthalten und wird vom Root-Klassenladeprogramm geladen. Die Implementierungsbibliotheken verschiedener Datenbankanbieter werden als Abhängigkeiten von Drittanbietern eingeführt. Dieser Teil der Implementierungsbibliotheken wird vom Loader der Anwendungsklasse geladen.

Code, um eine MySQL-Verbindung zu erhalten:

// Lade den Treiber

Class.forName("com.mysql.jdbc.Driver");

// Verbindung zur Datenbank herstellen

Connection conn = DriverManager.getConnection(url, user, password);

DriverManager wird vom Lader der Startklasse geladen. Der von ihm verwendete Datenbanktreiber (com.mysql.jdbc.Driver) wird vom Loader der Anwendungsklasse geladen. Dies ist die typische Klasse, die vom Loader der übergeordneten Klasse geladen wird und auf die die Unterklasse zugreift. Die vom Gerät geladene Klasse.

Informationen zur Realisierung dieses Prozesses finden Sie im Quellcode der DriverManager-Klasse:

//建立数据库连接底层方法
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    //获取调用者的类加载器
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        //由启动类加载器加载的类,该值为null,使用上下文类加载器
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
```java
    //...

    for(DriverInfo aDriver : registeredDrivers) {
        //使用上下文类加载器去加载驱动
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                //加载成功,则进行连接
                Connection con = aDriver.driver.connect(url, info);
                //...
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } 
        //...
    }
}
在上面的代码中留意改行代码:

```java
callerCL = Thread.currentThread().getContextClassLoader();

Diese Codezeile ruft den ContextClassLoader aus dem aktuellen Thread ab, und wo ist der ContextClassLoader festgelegt? Es ist im obigen Launcher-Quellcode festgelegt:

// Setzen Sie den
Kontextklassenlader Thread.currentThread (). SetContextClassLoader (this.loader); Auf
diese Weise ist der sogenannte Kontextklassenlader im Wesentlichen ein Anwendungsklassenlader. Daher ist der Kontextklassenlader nur ein Konzept, das vorgeschlagen wird, um den Rückwärtszugriff der Klasse zu lösen. Er ist kein brandneuer Klassenlader, sondern im Wesentlichen ein Anwendungsklassenlader.

Veröffentlicht 37 Originalartikel · Gelobt 6 · Besuche 4641

Ich denke du magst

Origin blog.csdn.net/littlewhitevg/article/details/105521641
Empfohlen
Rangfolge