Tailles de différents types d'objets Java

Présentation


L'objet Java contient-il:

  • champs dĂ©clarĂ©s en superclasse?
  • champs privĂ©s dĂ©clarĂ©s dans une superclasse?
  • mĂ©thodes?
  • Ă©lĂ©ments de tableau?
  • longueur du tableau?
  • un autre objet (en soi)?
  • code de hachage?
  • type (propre)?
  • nom (propre)?

Les rĂ©ponses Ă  ces questions (et Ă  d'autres) peuvent ĂȘtre obtenues en utilisant la bibliothĂšque de classes org.openjdk.jol, qui, en particulier, nous permet de comprendre que l'objet est une zone mĂ©moire:

  • contenant:
    • en-tĂȘte (jusqu'Ă  16 octets), et dedans:
      • code de hachage
      • rĂ©fĂ©rence de type
      • longueur du tableau (pour le tableau)
    • tous les champs (y compris privĂ©s) dĂ©clarĂ©s dans toutes les superclasses
    • ou Ă©lĂ©ments de tableau (pour un tableau)
  • ne contenant pas:
    • variables statiques
    • les mĂ©thodes
    • d'autres objets en vous
    • propre nom (c'est-Ă -dire que l'objet n'a pas de nom)


La préparation


Voici les résultats de l'évaluation de la mémoire d'objets de différents types par la méthode de la description du package java.lang.instrument (voir aussi ici ). Ces résultats nous permettent de répondre à la plupart des questions posées ci-dessus.

Les Ă©tapes suivantes doivent ĂȘtre accomplies:

  1. Créez une classe d'agent contenant la méthode premain:
    public static void premain(String, Instrumentation) {...} 
  2. Créez une archive contenant la classe d'agent et le fichier manifeste avec le contenu:
     Premain-class: -- 
  3. Créez une classe exécutable pour évaluer la mémoire.
  4. Spécifiez l'archive avec le paramÚtre "-javaagent" lors du démarrage de la machine virtuelle:
     java -javaagent:- -- 


Commençons par un cas de test. Pour plus de simplicité, nous utilisons un package sans nom.

Étape 1. CrĂ©ez une classe d'agent d'essai


 import java.lang.instrument.Instrumentation; public class A { public static void premain(String notUsedHere, Instrumentation i) { System.out.println("premain"); } } 

Nous compilons:

 javac A.java 

Étape 2. CrĂ©ez un fichier manifeste m.txt contenant:


 Premain-class: A   

ATTENTION: la deuxiĂšme ligne du fichier doit ĂȘtre VIDE, NE CONTENANT PAS D'ESPACES.

Créez l'archive A.jar:

 jar cmf m.txt A.jar A.class 

Étape 3. CrĂ©ez une classe exĂ©cutable d'essai


 public class M { public static void main(String[] notUsedHere) { //     System.out.println("main"); } } 

Nous compilons:

 javac M.java 

Étape 4. Effectuer


 java -javaagent:A.jar M 

RĂ©sultat:
 premain main 

indique que la méthode premain de la classe d'agent a été appelée en premier, puis la méthode principale de la classe exécutable.

Créez maintenant la classe d'agent requise:

 import java.lang.instrument.Instrumentation; public class A { //      private static Instrumentation ins; public static void premain(String notUsedHere, Instrumentation i) { ins = i; } public static Instrumentation instrumentation() {return ins;} } 

et classe exécutable:

 class M { public static void main(String[] notUsedHere) { mem("Object", new Object()); } private static void mem(Object o, Object ref) { System.out.println(o + ": " + objectBytesEstimate(ref)); } private static long objectBytesEstimate(Object ref) { if (A.instrumentation() == null) { throw new RuntimeException("Not initialized instrumentation."); } return A.instrumentation().getObjectSize(ref); } } 

La méthode

 long getObjectSize(Object --) 

renvoie une ESTIMATION de la taille (nombre d'octets) de la mĂ©moire occupĂ©e par l'objet au niveau du lien spĂ©cifiĂ©. Il faut garder Ă  l'esprit que l'estimation rĂ©sultante peut ĂȘtre diffĂ©rente pour une autre machine virtuelle. Les valeurs pour jdk-13 seront listĂ©es ici .

Nous réalisons:

 javac *.java jar cmf m.txt A.jar A.class java -javaagent:A.jar M 

et nous obtenons le résultat:

 Object: 16 

montrant qu'un objet VIDE de type Objet occupe ici (PAR ÉVALUATION) 16 octets. Parmi ceux-ci, 12 octets occupent l'en-tĂȘte et 4 octets Ă  la fin servent Ă  aligner la longueur de l'objet Ă  la limite de 8 octets.

RĂ©sultats


D'autres exemples ne contiendront que le code placĂ© dans la mĂ©thode principale de la classe M. Ils doivent ĂȘtre exĂ©cutĂ©s pour chaque exemple avec les commandes:

 javac M.java java -javaagent:A.jar M 

Il n'est pas nécessaire de recréer A.jar.

Par exemple, pour obtenir une estimation de la taille de la mémoire d'un objet de type arbitraire sans champs, nous mettons le code dans la méthode principale:

 class C {}; mem("Empty", new C()); // Empty: 16 

Le résultat indiqué dans le commentaire montre qu'un objet sans champs occupe autant d'octets qu'un objet de type Object.

De plus, le résultat du programme:

 {class C {int a; } mem(1, new C());} // 1: 16 {class C {int a,b; } mem(2, new C());} // 2: 24 {class C {int a,b,c; } mem(3, new C());} // 3: 24 {class C {int a,b,c,d;} mem(4, new C());} // 4: 32 

indique que chaque champ int prend 4 octets. Je note qu'ici chaque ligne est un bloc sĂ©parĂ©, ce qui vous permet d'utiliser le mĂȘme nom pour diffĂ©rentes classes.

Chaque champ long fait 8 octets:

 {class C {long a; } mem(1, new C());} // 1: 24 {class C {long a,b; } mem(2, new C());} // 2: 32 {class C {long a,b,c;} mem(3, new C());} // 3: 40 

Chaque champ booléen prend 1 octet (pour cette VM):

 {class C {boolean a; } mem(1, new C());} // 1: 16 {class C {boolean a,b; } mem(2, new C());} // 2: 16 {class C {boolean a,b,c; } mem(3, new C());} // 3: 16 {class C {boolean a,b,c,d; } mem(4, new C());} // 4: 16 {class C {boolean a,b,c,d,e;} mem(5, new C());} // 5: 24 

Chaque champ de référence prend 4 octets (pour cette VM):

 {class C {Boolean a; } mem(1, new C());} // 1: 16 {class C {Integer a; } mem(1, new C());} // 1: 16 {class C {Long a; } mem(1, new C());} // 1: 16 {class C {C a; } mem(1, new C());} // 1: 16 {class C {Boolean a,b; } mem(2, new C());} // 2: 24 {class C {Integer a,b; } mem(2, new C());} // 2: 24 {class C {Long a,b; } mem(2, new C());} // 2: 24 {class C {C a,b; } mem(2, new C());} // 2: 24 {class C {Boolean a,b,c; } mem(3, new C());} // 3: 24 {class C {Integer a,b,c; } mem(3, new C());} // 3: 24 {class C {Long a,b,c; } mem(3, new C());} // 3: 24 {class C {C a,b,c; } mem(3, new C());} // 3: 24 {class C {Boolean a,b,c,d;} mem(4, new C());} // 4: 32 {class C {Integer a,b,c,d;} mem(4, new C());} // 4: 32 {class C {Long a,b,c,d;} mem(4, new C());} // 4: 32 {class C {C a,b,c,d;} mem(4, new C());} // 4: 32 

Un champ de type chaßne prend également 4 octets, comme toute référence:

 {class C {String a; } mem(" null", new C());} // null: 16 {class C {String a=""; } mem(" empty", new C());} // empty: 16 {class C {String a="A"; } mem("1-char", new C());} // 1-char: 16 {class C {String a="1234567";} mem("7-char", new C());} // 7-char: 16 

Un champ de référence de tableau prend également 4 octets, comme chaque référence:

 {class C {int[] a; } mem("null", new C());} // null: 16 {class C {int[] a = {}; } mem(" 0", new C());} // 0: 16 {class C {int[] a = new int[1]; } mem(" 1", new C());} // 1: 16 {class C {int[] a = new int[7]; } mem(" 7", new C());} // 7: 16 {class C {int[][] a = {}; } mem(" 00", new C());} // 00: 16 {class C {int[][] a = new int[1][1];} mem(" 11", new C());} // 11: 16 {class C {int[][] a = new int[7][7];} mem(" 77", new C());} // 77: 16 

L'objet de sous-type contient chaque champ déclaré dans la superclasse, quel que soit le modificateur d'accÚs:

 {class S { } class C extends S {long a;} mem("0+1", new C());} // 0+1: 24 {class S {private long a;} class C extends S { } mem("1+0", new C());} // 1+0: 24 

L'objet de sous-type contient un champ dĂ©clarĂ© dans la superclasse avec le mĂȘme nom que dans la sous-classe (le soi-disant cachĂ© - cachĂ©):

 {class S { } class C extends S {long a,b;} mem("0+2", new C());} // 0+2: 32 {class S {long a;} class C extends S {long a; } mem("1+1", new C());} // 1+1: 32 

Un objet de sous-type contient chaque champ déclaré dans chacune de ses superclasses:

 class U {private long a; } class S extends U {private long a; } class C extends S { long a; } mem("1+1+1", new C()); // 1+1+1: 40 class D { long a,b,c;} mem("0+0+3", new D()); // 0+0+3: 40 

Tournez-vous vers les tableaux. Comme vous le savez, un tableau est un type spĂ©cial d'objet dont les Ă©lĂ©ments se trouvent dans l'objet lui-mĂȘme, donc la taille de la mĂ©moire occupĂ©e par le tableau croĂźt avec le nombre d'Ă©lĂ©ments:

 {long[] a = new long[ 0]; mem(" 0", a);} // 0: 16 {long[] a = new long[ 1]; mem(" 1", a);} // 1: 24 {long[] a = new long[ 2]; mem(" 2", a);} // 2: 32 {long[] a = new long[ 3]; mem(" 3", a);} // 3: 40 {long[] a = new long[100]; mem("100", a);} // 100: 816 

Et pour le tableau de liens:

 {Long[] a = new Long[ 0]; mem(" 0", a);} // 0: 16 {Long[] a = new Long[ 1]; mem(" 1", a);} // 1: 24 {Long[] a = new Long[ 2]; mem(" 2", a);} // 2: 24 {Long[] a = new Long[ 3]; mem(" 3", a);} // 3: 32 {Long[] a = new Long[100]; mem("100", a);} // 100: 416 

Maintenant, par curiosité, nous comparons les tailles de plusieurs objets de différents types:

 mem(" Object", new Object()); // Object: 16 mem(" String", new String("ABC")); // String: 24 mem(" Exception", new Exception()); // Exception: 40 mem(" int.class", int.class); // int.class: 112 mem(" int[].class", int[].class); // int[].class: 112 mem("Object.class", Object.class); // Object.class: 112 mem("System.class", System.class); // System.class: 160 mem("String.class", String.class); // String.class: 136 

De mĂȘme pour diffĂ©rents jdk sur un processeur 64 bits:

                 jdk1.6.0_45 jdk1.7.0_80 jdk1.8.0_191 jdk-9 jdk-12 jdk-13
                 ----------- ----------- ------------ ------ ------ ---- -
       Objet: 16 16 16 16 16 16
       ChaĂźne: 32 24 24 24 24 24
    Exception: 32 32 32 40 40 40
    classe int .: 104 88104112104104112
  int []. classe: 584 544 480 112 104 104 112
 Classe d'objet: 600560496112104112
 System.class: 624 560 496 144 152 160 160
 String.class: 696 640 624 136 128 136 

L'estimation de la taille de chaĂźne est de 24 octets, bien que la classe String contienne de nombreuses variables statiques, mĂ©thodes statiques et non statiques. Cela indique sans aucun doute l'absence de variables statiques et de code de mĂ©thode dans l'objet. Il en va de mĂȘme pour un objet de tout type.

En conclusion, un rappel: toutes les données sur la taille de l'objet sont estimées et elles peuvent varier dans une certaine mesure d'une exécution à l'autre et, bien sûr, pour différentes machines virtuelles.

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


All Articles