Comment obtenir la taille d'un objet en Java

1. Vue d'ensemble

Contrairement à C / C ++ où nous pouvons utiliser la méthode sizeof () pour obtenir une taille d'objet en octets, il n'y a pas de véritable équivalent d'une telle méthode en Java.

Dans cet article, nous allons montrer comment nous pouvons toujours obtenir la taille d'un objet particulier.

2. Consommation de mémoire en Java

Bien qu'il n'y ait pas d' opérateur sizeof en Java, nous n'en avons en fait pas besoin. Tous les types primitifs ont une taille standard et il n'y a généralement pas d'octets de remplissage ou d'alignement. Pourtant, ce n'est pas toujours simple.

Bien que les primitives doivent se comporter comme si elles avaient les tailles officielles, une JVM peut stocker des données de la manière qui lui plaît en interne, avec n'importe quelle quantité de remplissage ou de surcharge . Il peut choisir de stocker un booléen [] dans des blocs longs de 64 bits comme BitSet , d'allouer des objets temporaires sur la pile ou d'optimiser certaines variables ou des appels de méthode totalement inexistants en les remplaçant par des constantes, etc. Mais, tant que le programme donne le même résultat, c'est parfaitement bien.

En tenant également compte de l'impact du matériel et des caches du système d'exploitation (nos données pourraient être dupliquées à chaque niveau de cache), cela signifie que nous ne pouvons prédire approximativement la consommation de RAM .

2.1. Objets, références et classes de wrapper

La taille minimale de l'objet est de 16 octets pour le JDK 64 bits moderne, car l'objet a un en-tête de 12 octets, complété à un multiple de 8 octets. Dans le JDK 32 bits, la surcharge est de 8 octets, complétée à un multiple de 4 octets.

Les références ont une taille typique de 4 octets sur les plates-formes 32 bits et sur les plates-formes 64 bits avec une limite de tas inférieure à 32 Go ( -Xmx32G ) et 8 octets pour cette limite supérieure à 32 Go .

Cela signifie qu'une JVM 64 bits nécessite généralement 30 à 50% d'espace de tas supplémentaire.

Il est particulièrement pertinent de noter que les types en boîte, les tableaux, les chaînes et autres conteneurs comme les tableaux multidimensionnels sont coûteux en mémoire car ils ajoutent une certaine surcharge . Par exemple, lorsque nous comparons la primitive int (qui ne consomme que 4 octets) à l' objet Integer qui prend 16 octets, nous voyons qu'il y a une surcharge mémoire de 300%.

3. Estimation de la taille de l'objet à l'aide de l'instrumentation

Une façon d'obtenir une estimation de la taille d'un objet en Java consiste à utiliser la méthode getObjectSize (Object) de l' interface Instrumentation introduite dans Java 5.

Comme nous avons pu le voir dans la documentation Javadoc, la méthode fournit une «approximation spécifique à l'implémentation» de la taille de l'objet spécifié. Il est à noter qu'il existe une inclusion potentielle de surcharge dans la taille et que les valeurs peuvent être différentes lors d'un seul appel JVM.

Cette approche prend uniquement en charge l'estimation de la taille de l'objet considéré lui-même et non la taille des objets auxquels il fait référence . Pour estimer une taille totale de l'objet, nous aurions besoin d'un code qui passerait en revue ces références et calculerait la taille estimée.

3.1. Création d'un agent d'instrumentation

Afin d'appeler Instrumentation.getObjectSize (Object) pour obtenir la taille de l'objet, nous devons d'abord pouvoir accéder à l'instance d'Instrumentation. Nous devons utiliser l'agent d'instrumentation et il y a deux façons de le faire, comme décrit dans la documentation du paquet java.lang.instrument .

L'agent d'instrumentation peut être spécifié via la ligne de commande ou nous pouvons l'utiliser avec une JVM déjà en cours d'exécution . Nous allons nous concentrer sur le premier.

Pour spécifier l'agent d'instrumentation via la ligne de commande , nous aurons besoin de l'implémentation de la méthode premain surchargée qui sera d'abord appelée par la JVM lors de l'utilisation de l'instrumentation. En plus de cela, nous devons exposer une méthode statique pour pouvoir accéder à Instrumentation.getObjectSize (Object) .

Créons maintenant la classe InstrumentationAgent :

public class InstrumentationAgent { private static volatile Instrumentation globalInstrumentation; public static void premain(final String agentArgs, final Instrumentation inst) { globalInstrumentation = inst; } public static long getObjectSize(final Object object) { if (globalInstrumentation == null) { throw new IllegalStateException("Agent not initialized."); } return globalInstrumentation.getObjectSize(object); } }

Avant de créer un JAR pour cet agent, nous devons nous assurer qu'un métafichier simple, MANIFEST.MF y est inclus :

Premain-class: com.baeldung.objectsize.InstrumentationAgent

Nous pouvons maintenant créer un fichier JAR d'agent avec le fichier MANIFEST.MF inclus. Une façon est via la ligne de commande:

javac InstrumentationAgent.java jar cmf MANIFEST.MF InstrumentationAgent.jar InstrumentationAgent.class

3.2. Exemple de classe

Voyons cela en action en créant une classe avec des exemples d'objets qui utiliseront notre classe d'agent:

public class InstrumentationExample { public static void printObjectSize(Object object) { System.out.println("Object type: " + object.getClass() + ", size: " + InstrumentationAgent.getObjectSize(object) + " bytes"); } public static void main(String[] arguments) { String emptyString = ""; String string = "Estimating Object Size Using Instrumentation"; String[] stringArray = { emptyString, string, "com.baeldung" }; String[] anotherStringArray = new String[100]; List stringList = new ArrayList(); StringBuilder stringBuilder = new StringBuilder(100); int maxIntPrimitive = Integer.MAX_VALUE; int minIntPrimitive = Integer.MIN_VALUE; Integer maxInteger = Integer.MAX_VALUE; Integer minInteger = Integer.MIN_VALUE; long zeroLong = 0L; double zeroDouble = 0.0; boolean falseBoolean = false; Object object = new Object(); class EmptyClass { } EmptyClass emptyClass = new EmptyClass(); class StringClass { public String s; } StringClass stringClass = new StringClass(); printObjectSize(emptyString); printObjectSize(string); printObjectSize(stringArray); printObjectSize(anotherStringArray); printObjectSize(stringList); printObjectSize(stringBuilder); printObjectSize(maxIntPrimitive); printObjectSize(minIntPrimitive); printObjectSize(maxInteger); printObjectSize(minInteger); printObjectSize(zeroLong); printObjectSize(zeroDouble); printObjectSize(falseBoolean); printObjectSize(Day.TUESDAY); printObjectSize(object); printObjectSize(emptyClass); printObjectSize(stringClass); } public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } }

Pour que cela fonctionne, nous devons inclure l' option - javaagent avec le chemin vers l'agent JAR lors de l'exécution de notre application :

VM Options: -javaagent:"path_to_agent_directory\InstrumentationAgent.jar"

La sortie de l'exécution de notre classe nous montrera les tailles d'objets estimées:

Object type: class java.lang.String, size: 24 bytes Object type: class java.lang.String, size: 24 bytes Object type: class [Ljava.lang.String;, size: 32 bytes Object type: class [Ljava.lang.String;, size: 416 bytes Object type: class java.util.ArrayList, size: 24 bytes Object type: class java.lang.StringBuilder, size: 24 bytes Object type: class java.lang.Integer, size: 16 bytes Object type: class java.lang.Integer, size: 16 bytes Object type: class java.lang.Integer, size: 16 bytes Object type: class java.lang.Integer, size: 16 bytes Object type: class java.lang.Long, size: 24 bytes Object type: class java.lang.Double, size: 24 bytes Object type: class java.lang.Boolean, size: 16 bytes Object type: class com.baeldung.objectsize.InstrumentationExample$Day, size: 24 bytes Object type: class java.lang.Object, size: 16 bytes Object type: class com.baeldung.objectsize.InstrumentationExample$1EmptyClass, size: 16 bytes Object type: class com.baeldung.objectsize.InstrumentationExample$1StringClass, size: 16 bytes

4. Conclusion

Dans cet article, nous avons décrit comment la mémoire est utilisée par des types particuliers en Java, comment JVM stocke les données et souligné les éléments qui peuvent avoir un impact sur la consommation totale de mémoire. Nous avons ensuite montré comment nous pouvons en pratique obtenir la taille estimée des objets Java.

Comme toujours, le code complet lié à cet article se trouve dans le projet GitHub.