JVM-Interna, Teil 2 - Dateistruktur der Klasse

Hallo allerseits! Eine Übersetzung des Artikels wurde speziell für Studenten des Java-Entwicklerkurses erstellt .




Wir werden weiterhin darüber sprechen, wie die Java Virtual Machine intern funktioniert. Im vorherigen Artikel ( Original in Englisch ) haben wir uns mit dem Subsystem zum Laden von Klassen befasst. In diesem Artikel werden wir über die Struktur von Klassendateien sprechen.

Wie wir bereits wissen, wird der gesamte in der Java-Programmiersprache geschriebene Quellcode zunächst mit dem Java-Compiler, der Teil des Java Development Kit ist, in Bytecode kompiliert. Der Bytecode wird in einer Binärdatei in einer speziellen Klassendatei gespeichert. Dann werden diese Klassendateien dynamisch (falls erforderlich) vom Klassenladeprogramm (ClassLoader) in den Speicher geladen.


Abbildung - Java-Quellcode-Kompilierung

Jede Datei mit der .java in mindestens eine .class Datei kompiliert. Für jede im Quellcode definierte Klasse, Schnittstelle und jedes Modul wird eine .class Datei erstellt. Dies gilt auch für Interfaces und verschachtelte Klassen.

Hinweis - Der Einfachheit halber werden Dateien mit der Erweiterung .class als "Klassendateien" bezeichnet.

Schreiben wir ein einfaches Programm.

 public class ClassOne{ public static void main(String[] args){ System.out.println("Hello world"); } static class StaticNestedClass{ } } class ClassTwo{ } interface InterfaceOne{ } 

Wenn Sie javac für diese Datei javac werden die folgenden Dateien angezeigt.

 ClassOne$StaticNestedClass.class ClassOne.class ClassTwo.class InterfaceOne.class 

Wie Sie sehen, wird für jede Klasse und Schnittstelle eine separate Klassendatei erstellt.

Was ist in der Klassendatei?


Die Klassendatei ist im Binärformat. Die darin enthaltenen Informationen werden in der Regel ohne Einrückung zwischen aufeinanderfolgenden Informationen geschrieben. Alle Informationen sind an Byte-Grenzen ausgerichtet. Alle 16-Bit- und 32-Bit-Werte werden mit zwei oder vier aufeinander folgenden 8-Bit-Bytes geschrieben.

Die Klassendatei enthält die folgenden Informationen.

Magische Nummer, Unterschrift . Die ersten vier Bytes jeder Klassendatei sind immer 0xCAFEBABE . Diese vier Bytes identifizieren die Java-Klassendatei.

Dateiversion. Die nächsten vier Bytes enthalten die Haupt- und Nebenversion der Datei. Zusammen bestimmen diese Zahlen die Version des Klassendateiformats. Wenn die Klassendatei eine Hauptversion von M und eine Nebenversion von m enthält, wird diese Version als M bezeichnet

Jede JVM unterliegt Einschränkungen für die unterstützten Versionen von Klassendateien. Beispielsweise unterstützt Java 11 Hauptversionen von 45 bis 55, Java 12 - von 45 bis 56.

Pool von Konstanten. Eine Tabelle mit Strukturen, die Zeichenfolgenkonstanten, Klassennamen, Schnittstellen, Felder, Methoden und andere Konstanten in der ClassFile-Struktur und ihren Unterstrukturen darstellen. Jedes Konstantenpoolelement beginnt mit einem Einzelbyte-Tag, das den Konstantentyp definiert. Abhängig vom Typ der Konstante können die folgenden Bytes ein unmittelbarer Konstantenwert oder ein Verweis auf ein anderes Element im Pool sein.

Zugriff auf Flags. Eine Liste von Flags, die angeben, dass die Klasse entweder eine öffentliche oder eine private Schnittstelle ist, die letzte Klasse oder nicht. Verschiedene Flags wie ACC_PUBLIC , ACC_FINAL , ACC_INTERFACE , ACC_ENUM usw. sind in der Java Virtual Machine Specification beschrieben.

Diese Klasse. Link zum Eintrag im Konstantenpool.

Super klasse. Link zum Eintrag im Konstantenpool.

Schnittstellen Die Anzahl der von der Klasse implementierten Schnittstellen.

Die Anzahl der Felder. Die Anzahl der Felder in der Klasse oder der Schnittstelle.

Felder. Nach der Anzahl der Felder folgt eine Tabelle mit Strukturen variabler Länge. Eine für jedes Feld mit einer Beschreibung des Feldtyps und des Namens (unter Bezugnahme auf den Konstantenpool).

Anzahl der Methoden. Die Anzahl der Methoden in der Klasse oder der Schnittstelle. Diese Zahl enthält nur Methoden, die explizit in der Klasse definiert sind, ohne von Superklassen geerbte Methoden.

Methoden Weiter sind die Methoden selbst. Für jede Methode sind folgende Informationen enthalten: der Methodendeskriptor (Rückgabetyp und Argumentliste), die Anzahl der für die lokalen Variablen der Methode benötigten Wörter, die maximale Anzahl der für den Methodenoperandenstapel benötigten Stapelwörter, die von der Methode erfasste Ausnahmetabelle, Methodenbytecodes und die Tabelle Zeilennummern.

Die Anzahl der Attribute. Die Anzahl der Attribute in dieser Klasse, Schnittstelle oder diesem Modul.

Attribute Der Anzahl der Attribute folgen Tabellen oder Strukturen variabler Länge, die die einzelnen Attribute beschreiben. Beispielsweise gibt es immer ein Attribut "SourceFile". Es enthält den Namen der Quelldatei, aus der die Klassendatei kompiliert wurde.

Obwohl die Klassendatei nicht direkt von Menschen gelesen werden kann , gibt es im JDK ein Tool namens javap , das den Inhalt in einem praktischen Format anzeigt.

Schreiben wir ein einfaches Java-Programm wie unten gezeigt.

 package bytecode; import java.io.Serializable; public class HelloWorld implements Serializable, Cloneable { public static void main(String[] args) { System.out.println("Hello World"); } } 

Kompilieren wir dieses Programm mit javac , das die HelloWorld.class Datei erstellt, und verwenden javap , um die HelloWorld.class Datei javap . javap mit der Option -v (verbose) für javap das folgende Ergebnis:

 Classfile /Users/apersiankite/Documents/code_practice/java_practice/target/classes/bytecode/HelloWorld.class Last modified 02-Jul-2019; size 606 bytes MD5 checksum 6442d93b955c2e249619a1bade6d5b98 Compiled from "HelloWorld.java" public class bytecode.HelloWorld implements java.io.Serializable,java.lang.Cloneable minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // bytecode/HelloWorld super_class: #6 // java/lang/Object interfaces: 2, fields: 0, methods: 2, attributes: 1 Constant pool: #1 = Methodref #6.#22 // java/lang/Object."<init>":()V #2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #25 // Hello World #4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #28 // bytecode/HelloWorld #6 = Class #29 // java/lang/Object #7 = Class #30 // java/io/Serializable #8 = Class #31 // java/lang/Cloneable #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 LineNumberTable #13 = Utf8 LocalVariableTable #14 = Utf8 this #15 = Utf8 Lbytecode/HelloWorld; #16 = Utf8 main #17 = Utf8 ([Ljava/lang/String;)V #18 = Utf8 args #19 = Utf8 [Ljava/lang/String; #20 = Utf8 SourceFile #21 = Utf8 HelloWorld.java #22 = NameAndType #9:#10 // "<init>":()V #23 = Class #32 // java/lang/System #24 = NameAndType #33:#34 // out:Ljava/io/PrintStream; #25 = Utf8 Hello World #26 = Class #35 // java/io/PrintStream #27 = NameAndType #36:#37 // println:(Ljava/lang/String;)V #28 = Utf8 bytecode/HelloWorld #29 = Utf8 java/lang/Object #30 = Utf8 java/io/Serializable #31 = Utf8 java/lang/Cloneable #32 = Utf8 java/lang/System #33 = Utf8 out #34 = Utf8 Ljava/io/PrintStream; #35 = Utf8 java/io/PrintStream #36 = Utf8 println #37 = Utf8 (Ljava/lang/String;)V { public bytecode.HelloWorld(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 4: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lbytecode/HelloWorld; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 7: 0 line 8: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "HelloWorld.java" 

Hier können Sie sehen, dass die Klasse öffentlich ist und 37 Einträge im Konstantenpool hat. Es gibt ein Attribut (SourceFile unten), die Klasse implementiert zwei Schnittstellen (Serializable, Cloneable), sie hat keine Felder und es gibt zwei Methoden.

Möglicherweise haben Sie bemerkt, dass es nur eine statische Hauptmethode im Quellcode gibt, aber die Klassendatei besagt, dass es zwei Methoden gibt. Denken Sie an den Standardkonstruktor - dies ist ein Konstruktor ohne Argumente, der vom javac Compiler hinzugefügt wurde und dessen Bytecode auch in der Ausgabe sichtbar ist. Konstruktoren gelten als Methoden.

Sie können hier mehr über javap lesen.

Tipp : Mit javap können Sie auch feststellen, wie sich Lambdas von anonymen inneren Klassen unterscheiden.

Source: https://habr.com/ru/post/de478584/


All Articles