Quelle est la consommation de mémoire d'un objet en Java?


216

L'espace mémoire consommé par un objet avec 100 attributs est-il le même que celui de 100 objets, avec un attribut chacun?

Quelle quantité de mémoire est allouée à un objet?
Combien d'espace supplémentaire est utilisé lors de l'ajout d'un attribut?

Réponses:


180

Mindprod souligne que ce n'est pas une question simple à répondre:

Une machine virtuelle Java est libre de stocker des données comme bon lui semble en interne, grand ou petit endian, avec n'importe quelle quantité de remplissage ou de surcharge, bien que les primitives doivent se comporter comme si elles avaient les tailles officielles.
Par exemple, la JVM ou le compilateur natif peut décider de stocker un boolean[]morceau de 64 bits comme un BitSet. Il n'a pas à vous le dire, tant que le programme donne les mêmes réponses.

  • Il peut allouer des objets temporaires sur la pile.
  • Il peut optimiser certaines variables ou appels de méthode totalement inexistants en les remplaçant par des constantes.
  • Il peut s'agir de versions de méthodes ou de boucles, c'est-à-dire de compiler deux versions d'une méthode, chacune optimisée pour une certaine situation, puis de décider à l'avance laquelle appeler.

Ensuite, bien sûr, le matériel et le système d'exploitation ont des caches multicouches, sur le cache de puce, le cache SRAM, le cache DRAM, un ensemble de travail RAM ordinaire et un magasin de sauvegarde sur disque. Vos données peuvent être dupliquées à chaque niveau de cache. Toute cette complexité signifie que vous ne pouvez que prédire très approximativement la consommation de RAM.

Méthodes de mesure

Vous pouvez utiliser Instrumentation.getObjectSize()pour obtenir une estimation du stockage consommé par un objet.

Pour visualiser la disposition, l'empreinte et les références d'objets réels , vous pouvez utiliser l' outil JOL (Java Object Layout) .

En-têtes d'objet et références d'objet

Dans un JDK 64 bits moderne, un objet a un en-tête de 12 octets, complété à un multiple de 8 octets, donc la taille minimale de l'objet est de 16 octets. Pour les machines virtuelles Java 32 bits, la surcharge est de 8 octets, complétée par un multiple de 4 octets. (De la réponse de Dmitry Spikhalskiy , la réponse de Jayen , et JavaWorld .)

En règle générale, les références sont de 4 octets sur les plates-formes 32 bits ou sur les plates-formes 64 bits jusqu'à -Xmx32G; et 8 octets au-dessus de 32 Go ( -Xmx32G). (Voir les références aux objets compressés .)

Par conséquent, une machine virtuelle Java 64 bits nécessiterait généralement 30 à 50% d'espace de segment de mémoire en plus. ( Dois-je utiliser une JVM 32 ou 64 bits?, 2012, JDK 1.7)

Types, tableaux et chaînes encadrés

Les emballages en boîte ont des frais généraux par rapport aux types primitifs (de JavaWorld ):

  • Integer: Le résultat de 16 octets est un peu pire que ce à quoi je m'attendais car une intvaleur peut tenir dans seulement 4 octets supplémentaires. L'utilisation d'un an Integerme coûte 300% de mémoire supplémentaire par rapport au moment où je peux stocker la valeur en tant que type primitif

  • Long: 16 octets également: De toute évidence, la taille réelle de l'objet sur le tas est soumise à un alignement de mémoire de bas niveau effectué par une implémentation JVM particulière pour un type de processeur particulier. Il ressemble à Long8 octets de surcharge d'objet, plus 8 octets de plus pour la valeur longue réelle. En revanche, Integeravait un trou de 4 octets inutilisé, probablement parce que la JVM que j'utilise force l'alignement des objets sur une limite de mot de 8 octets.

D'autres conteneurs sont également coûteux:

  • Tableaux multidimensionnels : il offre une autre surprise.
    Les développeurs utilisent généralement des constructions commeint[dim1][dim2] dans le calcul numérique et scientifique.

    Dans une int[dim1][dim2]instance de tableau, chaque int[dim2]tableau imbriqué est un Objectà part entière. Chacun ajoute la surcharge de tableau 16 octets habituelle. Lorsque je n'ai pas besoin d'un tableau triangulaire ou irrégulier, cela représente un surdébit pur. L'impact augmente lorsque les dimensions de la matrice diffèrent considérablement.

    Par exemple, une int[128][2]instance prend 3 600 octets. Par rapport aux 1 040 octets qu'une int[256]instance utilise (qui a la même capacité), 3 600 octets représentent une surcharge de 246%. Dans le cas extrême de byte[256][1], le facteur de frais généraux est presque 19! Comparez cela à la situation C / C ++ dans laquelle la même syntaxe n'ajoute aucune surcharge de stockage.

  • String: Stringla croissance de la mémoire de a suit la croissance de sa matrice de caractères interne. Cependant, leString classe ajoute 24 octets supplémentaires de surcharge.

    Pour une Stringtaille non vide de 10 caractères ou moins, les frais généraux ajoutés par rapport à la charge utile (2 octets pour chaque caractère plus 4 octets pour la longueur), varient de 100 à 400%.

Alignement

Considérez cet exemple d'objet :

class X {                      // 8 bytes for reference to the class definition
   int a;                      // 4 bytes
   byte b;                     // 1 byte
   Integer c = new Integer();  // 4 bytes for a reference
}

Une somme naïve suggérerait qu'une instance de Xutiliserait 17 octets. Cependant, en raison de l'alignement (également appelé remplissage), la JVM alloue la mémoire par multiples de 8 octets, donc au lieu de 17 octets, elle allouerait 24 octets.


int [128] [6]: 128 tableaux de 6 ints - 768 ints au total, 3072 octets de données + 2064 octets Overhead d'objet = 5166 octets au total. int [256]: 256 pouces au total - donc non comparable. int [768]: 3072 octets de données + 16 octets de surcharge - environ 3 / 5ème de l'espace du tableau 2D - pas tout à fait 246% de surcharge!
JeeBee

Ah, l'article original utilisé int [128] [2] pas int [128] [6] - demandez-vous comment cela a changé. Montre également que des exemples extrêmes peuvent raconter une histoire différente.
JeeBee

2
La surcharge est de 16 octets dans les JVM 64 bits.
Tim Cooper du

3
@AlexWien: Certains schémas de récupération de place peuvent imposer une taille d'objet minimale distincte du remplissage. Lors de la récupération de place, une fois qu'un objet est copié d'un ancien emplacement vers un nouvel emplacement, l'ancien emplacement peut ne plus avoir besoin de conserver les données de l'objet, mais il devra contenir une référence au nouvel emplacement; il peut également être nécessaire de stocker une référence à l'ancien emplacement de l'objet dans lequel la première référence a été découverte et le décalage de cette référence dans l'ancien objet [car l'ancien objet peut toujours contenir des références qui n'ont pas encore été traitées].
supercat

2
@AlexWien: L'utilisation de la mémoire à l'ancien emplacement d'un objet pour contenir les informations de comptabilité du ramasse-miettes évite d'avoir à allouer de la mémoire à cette fin, mais peut imposer une taille d'objet minimale plus grande que celle qui serait autrement requise. Je pense qu'au moins une version de garbage collector .NET utilise cette approche; il serait certainement possible pour certains ramasse-miettes Java de le faire également.
supercat

34

Cela dépend de l'architecture / jdk. Pour une architecture JDK et 64 bits moderne, un objet a un en-tête de 12 octets et un remplissage de 8 octets - la taille minimale de l'objet est donc de 16 octets. Vous pouvez utiliser un outil appelé Java Object Layout pour déterminer une taille et obtenir des détails sur la disposition des objets et la structure interne de toute entité ou deviner ces informations par référence de classe. Exemple de sortie pour Integer sur mon environnement:

Running 64-bit HotSpot VM.
Using compressed oop with 3-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

java.lang.Integer object internals:
 OFFSET  SIZE  TYPE DESCRIPTION                    VALUE
      0    12       (object header)                N/A
     12     4   int Integer.value                  N/A
Instance size: 16 bytes (estimated, the sample instance is not available)
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

Ainsi, pour Integer, la taille de l'instance est de 16 octets, car 4 octets int compactés en place juste après l'en-tête et avant la limite de remplissage.

Échantillon de code:

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.util.VMSupport;

public static void main(String[] args) {
    System.out.println(VMSupport.vmDetails());
    System.out.println(ClassLayout.parseClass(Integer.class).toPrintable());
}

Si vous utilisez maven, pour obtenir JOL:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.3.2</version>
</dependency>

28

Chaque objet a une certaine surcharge pour son moniteur associé et les informations de type, ainsi que les champs eux-mêmes. Au-delà de cela, les champs peuvent être disposés à peu près mais la JVM le juge opportun (je crois) - mais comme indiqué dans une autre réponse , au moins certaines JVM seront assez compactes. Considérez une classe comme celle-ci:

public class SingleByte
{
    private byte b;
}

contre

public class OneHundredBytes
{
    private byte b00, b01, ..., b99;
}

Sur une machine virtuelle Java 32 bits, je m'attendrais à ce que 100 instances SingleByteprennent 1200 octets (8 octets de surcharge + 4 octets pour le champ en raison du remplissage / alignement). Je m'attends à ce qu'une instance de OneHundredBytesprenne 108 octets - la surcharge, puis 100 octets, compressés. Cela peut certainement varier selon la JVM - une implémentation peut décider de ne pas emballer les champs dansOneHundredBytes , ce qui lui fait prendre 408 octets (= 8 octets de surcharge + 4 * 100 octets alignés / remplis). Sur une machine virtuelle Java 64 bits, la surcharge peut également être plus importante (pas sûr).

EDIT: Voir le commentaire ci-dessous; apparemment, HotSpot remplit les limites de 8 octets au lieu de 32, de sorte que chaque instance de SingleByteprendrait 16 octets.

Quoi qu'il en soit, le "grand objet unique" sera au moins aussi efficace que plusieurs petits objets - pour des cas simples comme celui-ci.


9
En fait, une instance de SingleByte prendrait 16 octets sur une machine virtuelle Java Sun, soit 8 octets de surcharge, 4 octets pour le champ, puis 4 octets pour le remplissage des objets, car le compilateur HotSpot arrondit tout en multiples de 8.
Paul Wagland

6

La mémoire totale utilisée / libre d'un programme peut être obtenue dans le programme via

java.lang.Runtime.getRuntime();

Le runtime a plusieurs méthodes liées à la mémoire. L'exemple de codage suivant montre son utilisation.

package test;

 import java.util.ArrayList;
 import java.util.List;

 public class PerformanceTest {
     private static final long MEGABYTE = 1024L * 1024L;

     public static long bytesToMegabytes(long bytes) {
         return bytes / MEGABYTE;
     }

     public static void main(String[] args) {
         // I assume you will know how to create a object Person yourself...
         List < Person > list = new ArrayList < Person > ();
         for (int i = 0; i <= 100000; i++) {
             list.add(new Person("Jim", "Knopf"));
         }
         // Get the Java runtime
         Runtime runtime = Runtime.getRuntime();
         // Run the garbage collector
         runtime.gc();
         // Calculate the used memory
         long memory = runtime.totalMemory() - runtime.freeMemory();
         System.out.println("Used memory is bytes: " + memory);
         System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
     }
 }

6

Il semble que chaque objet a une surcharge de 16 octets sur les systèmes 32 bits (et 24 octets sur les systèmes 64 bits).

http://algs4.cs.princeton.edu/14analysis/ est une bonne source d'information. Un exemple parmi tant d'autres est le suivant.

entrez la description de l'image ici

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf est également très informatif, par exemple:

entrez la description de l'image ici


"Il semble que chaque objet a une surcharge de 16 octets sur les systèmes 32 bits (et 24 octets sur les systèmes 64 bits)." Ce n'est pas correct, du moins pour les JDK actuels. Jetez un oeil à ma réponse pour l'exemple Integer. La surcharge de l'objet est d'au moins 12 octets pour l'en-tête du système 64 bits et du JDK moderne. Peut être plus en raison du remplissage, dépend de la disposition réelle des champs dans l'objet.
Dmitry Spikhalskiy

Le deuxième lien vers le tutoriel Java efficace en mémoire semble être mort - je reçois "Interdit".
tsleyson

6

L'espace mémoire consommé par un objet avec 100 attributs est-il le même que celui de 100 objets, avec un attribut chacun?

Non.

Quelle quantité de mémoire est allouée à un objet?

  • La surcharge est de 8 octets sur 32 bits, 12 octets sur 64 bits; puis arrondi à un multiple de 4 octets (32 bits) ou 8 octets (64 bits).

Combien d'espace supplémentaire est utilisé lors de l'ajout d'un attribut?

  • Les attributs varient de 1 octet (byte) à 8 octets (long / double), mais les références sont soit 4 octets soit 8 octets en fonction pas si cette 32 bits ou 64 bits, mais si -Xmx est <32Gb ou> = 32 Go: typique 64 Les JVM à bits ont une optimisation appelée "-UseCompressedOops" qui comprime les références à 4 octets si le tas est inférieur à 32 Go.

1
un caractère est 16 bits, pas 8 bits.
comonad

tu as raison. quelqu'un semble avoir modifié ma réponse d'origine
Jayen

5

Non, l'enregistrement d'un objet nécessite également un peu de mémoire. 100 objets avec 1 attribut occuperont plus de mémoire.


4

La question sera très large.

Cela dépend de la variable de classe ou vous pouvez appeler comme état l'utilisation de la mémoire en java.

Il a également une certaine mémoire supplémentaire pour les en-têtes et le référencement.

La mémoire de tas utilisée par un objet Java comprend

  • mémoire pour les champs primitifs, selon leur taille (voir ci-dessous pour les tailles des types primitifs);

  • mémoire pour les champs de référence (4 octets chacun);

  • un en-tête d'objet, composé de quelques octets d'informations "domestiques";

Les objets en java nécessitent également des informations de "gestion", telles que l'enregistrement de la classe, de l'ID et des indicateurs d'état d'un objet, par exemple si l'objet est actuellement accessible, actuellement verrouillé par synchronisation, etc.

La taille de l'en-tête d'objet Java varie sur jvm 32 et 64 bits.

Bien que ce soient les principaux consommateurs de mémoire, jvm nécessite également des champs supplémentaires, parfois comme pour l'alignement du code, etc.

Tailles des types primitifs

booléen et octet - 1

char & short - 2

int & float - 4

long et double - 8


Les lecteurs peuvent également trouver cet article très éclairant: cs.virginia.edu/kim/publicity/pldi09tutorials/…
quellish



1

non, 100 petits objets nécessitent plus d'informations (mémoire) qu'un grand.


0

Les règles concernant la quantité de mémoire consommée dépendent de l'implémentation JVM et de l'architecture CPU (32 bits contre 64 bits par exemple).

Pour les règles détaillées de la JVM SUN, consultez mon ancien blog

Cordialement, Markus


Je suis presque sûr que Sun Java 1.6 64 bits, a besoin de 12 octets pour un objet simple + 4 padding = 16; un objet + un champ entier = 12 + 4 = 16
AlexWien

Avez-vous fermé votre blog?
Johan Boulé

Pas vraiment, pas sûr que les blogs SAP aient bougé. La majeure partie peut être trouvée ici kohlerm.blogspot.com
kohlerm
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.