JVM Internals, Part 2 - Class File Structure

Bonjour à tous! Une traduction de l'article a été préparée spécialement pour les étudiants du cours Java Developer .




Nous continuons à parler du fonctionnement interne de la machine virtuelle Java. Dans l' article précédent ( original en anglais ), nous avons examiné le sous-système de chargement de classe. Dans cet article, nous parlerons de la structure des fichiers de classe.

Comme nous le savons déjà, tout le code source écrit dans le langage de programmation Java est d'abord compilé en bytecode à l'aide du compilateur javac , qui fait partie du kit de développement Java. Le bytecode est stocké dans un fichier binaire dans un fichier de classe spécial. Ensuite, ces fichiers de classe sont dynamiquement (si nécessaire) chargés en mémoire par le chargeur de classe (ClassLoader).


Figure - Compilation du code source Java

Chaque fichier avec une .java compilé dans au moins un fichier .class . Pour chaque classe, interface et module défini dans le code source, un fichier .class est créé. Cela s'applique également aux interfaces et aux classes imbriquées.

Remarque - par souci de simplicité, les fichiers avec l'extension .class seront appelés «fichiers de classe».

Écrivons un programme simple.

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

L'exécution de javac pour ce fichier entraînera les fichiers suivants.

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

Comme vous pouvez le voir, un fichier de classe distinct est créé pour chaque classe et interface.

Que contient le fichier de classe?


Le fichier de classe est au format binaire. Les informations qu'il contient sont généralement écrites sans indentation entre des informations consécutives, tout est aligné sur les limites des octets. Toutes les valeurs 16 bits et 32 ​​bits sont écrites en utilisant deux ou quatre octets 8 bits consécutifs.

Le fichier de classe contient les informations suivantes.

Numéro magique, signature . Les quatre premiers octets de chaque fichier de classe sont toujours 0xCAFEBABE . Ces quatre octets identifient le fichier de classe Java.

Version du fichier. Les quatre octets suivants contiennent les versions principales et mineures du fichier. Ensemble, ces nombres déterminent la version du format de fichier de classe. Si le fichier de classe a une version majeure majeure de M et un m mineur, alors nous désignons cette version comme Mm

Chaque machine virtuelle Java a des limitations sur les versions prises en charge des fichiers de classe. Par exemple, Java 11 prend en charge les versions principales de 45 à 55, Java 12 - de 45 à 56.

Pool de constantes. Un tableau de structures représentant des constantes de chaîne, des noms de classe, des interfaces, des champs, des méthodes et d'autres constantes qui se trouvent dans la structure ClassFile et ses sous-structures. Chaque élément de pool de constantes commence par une balise à un octet qui définit le type de constante. Selon le type de constante, les octets suivants peuvent être une valeur de constante immédiate ou une référence à un autre élément du pool.

Drapeaux d'accès. Une liste d'indicateurs qui indiquent que la classe est soit une interface, publique ou privée, la classe finale ou non. Divers drapeaux tels que ACC_PUBLIC , ACC_FINAL , ACC_INTERFACE , ACC_ENUM , etc. sont décrits dans la spécification de machine virtuelle Java.

Cette classe. Lien vers l'entrée dans le pool constant.

Super classe. Lien vers l'entrée dans le pool constant.

Interfaces Le nombre d'interfaces implémentées par la classe.

Le nombre de champs. Le nombre de champs dans la classe ou l'interface.

Champs. Après le nombre de champs, une table de structures de longueur variable suit. Un pour chaque champ avec une description du type et du nom du champ (en référence au pool de constantes).

Nombre de méthodes. Le nombre de méthodes dans la classe ou l'interface. Ce nombre inclut uniquement les méthodes qui sont explicitement définies dans la classe, sans méthodes héritées des superclasses.

Les méthodes Viennent ensuite les méthodes elles-mêmes. Pour chaque méthode, les informations suivantes sont contenues: le descripteur de méthode (type de retour et liste d'arguments), le nombre de mots nécessaires pour les variables locales de la méthode, le nombre maximum de mots de pile nécessaires pour la pile d'opérandes de la méthode, la table des exceptions capturée par la méthode, les bytecodes de méthode et la table numéros de ligne.

Le nombre d'attributs. Le nombre d'attributs de cette classe, interface ou module.

Attributs Le nombre d'attributs est suivi de tableaux ou de structures de longueur variable qui décrivent chaque attribut. Par exemple, il y a toujours un attribut «SourceFile». Il contient le nom du fichier source à partir duquel le fichier de classe a été compilé.

Bien que le fichier de classe ne soit pas directement lisible par l'homme, il existe dans le JDK un outil appelé javap qui affiche son contenu dans un format pratique.

Écrivons un programme Java simple comme indiqué ci-dessous.

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

Compilons ce programme avec javac , qui créera le fichier HelloWorld.class , et utilisons javap pour afficher le fichier HelloWorld.class . L'exécution de javap avec l'option -v (verbose) pour HelloWorld.class donne le résultat suivant:

 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" 

Ici, vous pouvez voir que la classe est publique et a 37 entrées dans le pool constant. Il y a un attribut (SourceFile ci-dessous), la classe implémente deux interfaces (Serializable, Cloneable), elle n'a pas de champs et il y a deux méthodes.

Vous avez peut-être remarqué qu'il n'y a qu'une seule méthode principale statique dans le code source, mais le fichier de classe indique qu'il existe deux méthodes. Souvenez-vous du constructeur par défaut - il s'agit d'un constructeur sans argument ajouté par le compilateur javac , dont le bytecode est également visible dans la sortie. Les constructeurs sont considérés comme des méthodes.

Vous pouvez en savoir plus sur javap ici .

Astuce : vous pouvez également utiliser javap pour voir en quoi les lambdas diffèrent des classes internes anonymes.

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


All Articles