Heim >Java >javaLernprogramm >JVM-Klassenlademechanismus für Java-Lernen

JVM-Klassenlademechanismus für Java-Lernen

青灯夜游
青灯夜游nach vorne
2018-10-16 16:35:252034Durchsuche

In diesem Artikel wird Ihnen der Lademechanismus der JVM-Klasse beim Java-Lernen vorgestellt. Es hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen. Ich hoffe, es wird Ihnen hilfreich sein.

1. Übersicht

Die virtuelle Maschine lädt die Klassendatei (Binärbyte-Stream) in den Speicher, überprüft, konvertiert, analysiert und initialisiert die Daten und erstellt schließlich eine Datei, die verwendet werden kann Diese Prozessreihe wird direkt vom Java-Typ verwendet und ist der Lademechanismus der Klasse.

2. Zeitpunkt des Ladens einer Klasse

Eine Klasse beginnt mit dem Laden in den Speicher durch die virtuelle Maschine, bis sie aus dem Speicher entladen wird. Der gesamte Lebenszyklus umfasst: Laden – - Überprüfung – Vorbereitung – Analyse – Initialisierung – Verwendung – Deinstallation Diese 7 Phasen. Die drei Teile Verifizierung, Vorbereitung und Analyse werden zusammenfassend als Verbindungen bezeichnet.

Das Lebenszyklusdiagramm sieht wie folgt aus:

Die Reihenfolge der fünf Phasen Laden, Verifizieren, Vorbereiten, Initialisieren und Entladen wird festgelegt und die Der Ladevorgang der Klasse muss in dieser Reihenfolge beginnen, die Analysephase ist nicht unbedingt notwendig: In einigen Fällen kann sie nach der Initialisierung beginnen, dies dient auch der Unterstützung der dynamischen Bindung der Java-Sprache.

Welche Situationen können die Initialisierungsphase einer Klasse auslösen? (Voraussetzung: Laden, Verifizieren und Vorbereiten wurden natürlich ausgeführt)

  1. Wenn Sie auf die vier Anweisungen new, getstatic, putstatic und invokestatic stoßen, wird die Klasse initialisiert, wenn sie nicht initialisiert wurde wird ausgelöst. (Die häufigsten Szenarien, die diese vier Anweisungen bei der Arbeit auslösen: neue Instanziierungsobjekte, Lesen oder Festlegen statischer Felder der Klasse [mit Ausnahme endgültiger Änderungen oder statischer Felder, die in den Konstantenpool eingefügt wurden], Aufrufen statischer Methoden der Klasse)

  2. Bei Verwendung von Reflektion

  3. Wenn beim Initialisieren einer Klasse die übergeordnete Klasse noch nicht initialisiert wurde, müssen Sie auslösen Zuerst die Initialisierung der übergeordneten Klasse

  4. Wenn die virtuelle Maschine startet, müssen Sie eine auszuführende Hauptklasse angeben (die Klasse, die die Hauptmethode enthält). Die virtuelle Maschine wird zuerst initialisiert Diese Klasse

  5. Verwendung Wenn die dynamische Sprache jdk1.7 unterstützt wird, wenn das endgültige Analyseergebnis einer java.lang.invoke.MethodHandle-Instanz das Methodenhandle von REF_getStatic, REF_putStatic, REF_invokeStatic ist. Wenn die diesem Handle entsprechende Klasse nicht initialisiert wurde, müssen Sie zuerst ihre Initialisierung auslösen

Hinweis: Alle Referenzierungsmethoden der Klasse lösen beispielsweise keine Initialisierung aus : Erstellen eines Arrays, das auf endgültige geänderte Variablen und statische Variablen von Unterklassen verweist, die auf übergeordnete Klassen verweisen, löst jedoch keine Initialisierung der Unterklasse aus, sondern die Initialisierung der übergeordneten Klasse

3. Klassenladevorgang

- Laden

Das Laden ist eine Phase des Klassenladens. Während der Ladephase muss die virtuelle Maschine die folgenden drei Dinge ausführen:

  1. Den binären Bytestrom abrufen, der diese Klasse definiert vollständig qualifizierter Name der Klasse

  2. Konvertieren Sie die durch diesen Bytestream dargestellte statische Speicherstruktur in die Laufzeitdatenstruktur des Methodenbereichs

  3. Generieren eine Darstellung dieses Typs im Speicher Das java.lang.Object-Objekt dient als Zugriffspunkt für verschiedene Daten dieser Klasse im Methodenbereich

Im Vergleich zu anderen Stufen des Klassenladens ist die Ladephase (genauer gesagt, in der Ladephase Die Aktion zum Abrufen des binären Bytestroms einer Klasse) ist diejenige, die vom Entwickler am besten kontrolliert werden kann. Denn die Ladephase kann mit dem vom System bereitgestellten Boot-Klassenlader oder mit dem benutzerdefinierten Klassenlader des Entwicklers abgeschlossen werden (d. h. durch Überschreiben der LoadClass ()-Methode des Klassenladers).

Nach Abschluss des Ladevorgangs wird der externe binäre Bytestrom in das von der virtuellen Maschine benötigte Format konvertiert und im Methodenbereich gespeichert. Anschließend wird ein Objekt der Klasse java.lang.Class im Speicher instanziiert . Dieses Objekt dient dem Programm als externe Schnittstelle für den Zugriff auf diese Datentypen im Methodenbereich.

Ein Teil der Ladephase und die Verbindungsphase sind miteinander verflochten, und Überprüfungen und andere Vorgänge können erst durchgeführt werden, wenn der Ladevorgang abgeschlossen ist. Diese beim Laden eingeklemmten Aktionen gehören immer noch zur Verbindungsphase, und die Startzeiten dieser beiden Phasen behalten weiterhin eine feste Reihenfolge bei.

- Verifizierung

Die Verifizierung ist der erste Verbindungsschritt, um sicherzustellen, dass die im geladenen binären Bytestrom enthaltenen Informationen der Spezifikation der virtuellen Maschine entsprechen.

Die Verifizierungsphase ist grob in die folgenden 4 Verifizierungsaktionen unterteilt:

Überprüfung des Dateiformats : Überprüfen Sie, ob der Bytestream der Klassendateiformatspezifikation entspricht. Zum Beispiel: ob es mit der magischen Zahl 0xCAFEBABE beginnt, ob die Haupt- und Nebenversionsnummern im Verarbeitungsbereich der aktuellen virtuellen Maschine liegen, ob die Konstanten im Konstantenpool nicht unterstützte Typen haben ...

Metadatenüberprüfung: Führen Sie eine semantische Analyse der durch den Bytecode beschriebenen Informationen durch. Zum Beispiel: Verfügt diese Klasse über eine übergeordnete Klasse und ob sie die übergeordnete Klasse korrekt erbt.

Bytecode-Überprüfung: Durch die Analyse des Datenflusses und des Kontrollflusses wird festgestellt, dass die Programmsemantik legal und logisch ist (um es ganz klar auszudrücken, es bedeutet, den Methodenkörper des Programms zu analysieren Klasse, um sicherzustellen, dass sich die Methode in der virtuellen Maschine befindet, wenn sie ausgeführt wird).

Überprüfung der Symbolreferenz: Stellen Sie sicher, dass die Parsing-Aktion normal ausgeführt werden kann.

Die Verifizierungsphase ist sehr wichtig, aber nicht unbedingt eine notwendige Phase (da sie keinen Einfluss auf die Programmlaufzeit hat). Wenn der gesamte ausgeführte Code wiederholt verwendet und überprüft wurde, kann die Überprüfung während der Implementierungsphase mithilfe des Parameters -Xverify:none deaktiviert werden.

- Bereiten Sie

vor, um formal Speicher für Klassenvariablen zuzuweisen und den Anfangswert der Klassenvariablen festzulegen. Der von diesen Variablen verwendete Speicher wird im Methodenbereich zugewiesen.

Hinweis:

  • Zu diesem Zeitpunkt werden nur statische Variablen zugewiesen, keine Instanzvariablen werden zusammen mit der Objektinstanz zugewiesen Im Java-Heap

  • ist der Anfangswert normalerweise der Nullwert des Datentyps. Wenn Sie eine statische Variable public static int value = 123 definieren, ist der Anfangswert von value während der Vorbereitungsphase 0 statt 123.

  • Durch final geänderte Variablen werden während der Vorbereitungsphase auf den durch das Attribut angegebenen Wert initialisiert. Zum Beispiel: public static final int value = 123; dann ist der Anfangswert des Werts in der Vorbereitungsphase 123.

- Parsing

Die Parsing-Phase ist der Prozess, bei dem die virtuelle Maschine Symbolreferenzen im Konstantenpool durch direkte Referenzen ersetzt . Die Parsing-Aktion wird hauptsächlich für sieben Arten von Symbolreferenzen durchgeführt: Klassen oder Schnittstellen, Felder, Klassenmethoden, Schnittstellenmethoden, Methodentypen, Methodenhandles und Aufrufstellenqualifizierer.

Symbolreferenz: Verwenden Sie eine Reihe von Symbolen, um das Ziel der Referenz zu beschreiben. Das Symbol kann jede Form von Literal sein.

Direkter Verweis: ein Zeiger auf das Ziel, ein relativer Offset oder ein Handle, der das Ziel indirekt lokalisieren kann.

-Initialisierung

Die Initialisierungsphase ist der Prozess der Ausführung der Klassenkonstruktor()-Methode. In der Vorbereitungsphase wurde den Variablen ein vom System benötigter Anfangswert zugewiesen, und in der Initialisierungsphase werden Klassenvariablen und andere Ressourcen gemäß den vom Programmierer festgelegten Parameterwerten initialisiert.

Methode des Klassenkonstruktors(): Sie wird vom Compiler generiert, der automatisch die Zuweisungsaktionen aller Klassenvariablen in der Klasse sammelt und die Anweisungen im statischen Codeblock zusammenführt.

Die Reihenfolge, in der der Compiler sammelt, wird durch die Reihenfolge bestimmt, in der Anweisungen in der Quelldatei erscheinen. Ein statischer Codeblock kann nur auf Variablen zugreifen, die vor dem statischen Block definiert sind, auf Variablen, die danach definiert sind, und auf Variablen, die in definiert sind Dem vorangehenden statischen Block können Werte zugewiesen, aber nicht darauf zugegriffen werden.

非法向前引用示例

public class SuperClass {
    public static int va;
    static {
        value = 1;            //可以编译通过
        va = value;           //报错  非法向前引用
        System.out.println("父类初始化");
    }

    public static int value = 123;
}

Die Methode () ist für Klassen oder Schnittstellen nicht erforderlich. Wenn in einer Klasse kein statischer Codeblock vorhanden ist und keine Zuweisungsoperation zu Variablen vorhanden ist, muss der Compiler dies nicht tun. Von der Klasse generierte -Methode

Statische Blöcke können nicht in Schnittstellen verwendet werden, es können jedoch weiterhin Variablenzuweisungsvorgänge ausgeführt werden, sodass Schnittstellen und Klassen -Methoden generieren. Der Unterschied besteht darin, dass die Schnittstelleninitialisierung nicht zuerst die Initialisierung der übergeordneten Klasse erfordert. Die Initialisierung der übergeordneten Schnittstelle wird nur ausgelöst, wenn die Variablen in der übergeordneten Schnittstelle verwendet werden. Darüber hinaus löst die Implementierungsklasse der Schnittstelle nicht die Instanziierung der Schnittstelle aus.

Die virtuelle Maschine stellt sicher, dass die Methode () in mehreren Threads korrekt gesperrt und synchronisiert ist. Wenn mehrere Threads eine Klasse initialisieren, führt nur ein Thread die Klasse aus. ()-Methode befinden sich andere Threads im Wartezustand. Nur der aktive Thread kann die Ausführung abschließen. Wenn in der Methode () einer Klasse ein lang andauernder Vorgang vorhanden ist, kann dies dazu führen, dass mehrere Threads blockiert werden. In tatsächlichen Anwendungen ist diese Blockierung häufig sehr versteckt.

4. Klassenlader

Das Designteam für virtuelle Maschinen hat die Aktion „Erhalten des binären Bytestroms, der diese Klasse beschreibt, durch den vollständig qualifizierten Namen einer Klasse“ beim Klassenladen in Java umgesetzt außerhalb der virtuellen Maschine, sodass die Anwendung entscheiden kann, wie sie die erforderlichen Klassen erhält. Der Codeblock, der diese Aktion implementiert, wird als Klassenlader bezeichnet.

Aus Sicht von Java-Entwicklern werden Klassenlader grob in die folgenden drei Typen unterteilt

Bootstrap Classloader (Bootstrap Classloader) : Verantwortlich für die Speicherung in < lib (Javahome ist das JDK-Installationsverzeichnis) oder im durch den Parameter -Xbootclasspath angegebenen Pfad und wird von der virtuellen Maschine erkannt (erkennt nur den Dateinamen, z. B. rt.jar, Klassen, deren Namen nicht mit dem übereinstimmen). Die Klassenbibliothek wird nicht geladen, selbst wenn sie unter lib platziert wird. Die Klassenbibliothek wird in den Speicher der virtuellen Maschine geladen. Der Startup-Klassenlader kann nicht direkt von Java-Programmen verwendet werden.

Extension Classloader : Dieser Loader wird von sun.misc.Launcher$ExtClassLoader implementiert, der für das Laden des libext-Verzeichnisses verantwortlich ist, oder von Java Systempfad, der durch die Systemvariable ext.dirs angegeben wird. Entwickler können den Erweiterungsklassenlader direkt verwenden.

Application Classloader (Application Classloader) : Dieser Loader wird von sun.misc.Launcher$AppClassLoader implementiert, der für das Laden der im Benutzerklassenpfad (ClassPath) angegebenen Klassenbibliothek verantwortlich ist. Entwickler können diesen Loader direkt verwenden. Wenn in der Anwendung kein benutzerdefinierter Klassenlader vorhanden ist, ist dies der vom Programm standardmäßig ausgeführte Klassenlader. (Systemlader)

我们的应用程序都是由这3种类加载器相互配合进行加载的。如果有必要,还可以加入自定义的类加载器。

这些类加载器之间的关系如下图:

 

5.双亲委派模型: 

双亲委派模型的工作过程是:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该到达顶层的启动类加载器。只有当父加载无法完成这个加载请求时,子加载器才会尝试自己去加载。

双亲委派机制:

1、当ApplicationClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

ClassLoader源码分析:    

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 先检查此类是否已被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //委派给父类加载器去加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //如果没有父加载器,则调用启动类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //如果父加载器无法加载,则调用本身加载器去加载
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }                                         

双亲委派模型意义:

  • 系统类防止内存中出现多份同样的字节码

  • 保证Java程序安全稳定运行

参考

《深入理解Java虚拟机》 

总结:以上就是本篇文的全部内容,希望能对大家的学习有所帮助。更多相关教程请访问Java视频教程java开发图文教程bootstrap视频教程

Das obige ist der detaillierte Inhalt vonJVM-Klassenlademechanismus für Java-Lernen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen