En Java, quelle est la meilleure façon de déterminer la taille d’un objet ?

StackOverflow https://stackoverflow.com/questions/52353

  •  09-06-2019
  •  | 
  •  

Question

Par exemple, disons que j'ai une application capable de lire un fichier CSV contenant des piles de lignes de données.Je donne à l'utilisateur un résumé du nombre de lignes en fonction des types de données, mais je veux m'assurer de ne pas lire trop de lignes de données et provoquer OutOfMemoryErrors.Chaque ligne se traduit par un objet.Existe-t-il un moyen simple de connaître la taille de cet objet par programme ?Existe-t-il une référence qui définit la taille des types primitifs et des références d'objet pour un VM?

En ce moment, j'ai un code qui dit lire jusqu'à 32 000 lignes, mais j'aimerais aussi avoir du code qui dit de lire autant de lignes que possible jusqu'à ce que j'aie utilisé 32 Mo de mémoire.C'est peut-être une autre question, mais j'aimerais quand même savoir.

Était-ce utile?

La solution

Vous pouvez utiliser le paquet java.lang.instrument

Compilez et mettez cette classe dans un JAR :

import java.lang.instrument.Instrumentation;

public class ObjectSizeFetcher {
    private static Instrumentation instrumentation;

    public static void premain(String args, Instrumentation inst) {
        instrumentation = inst;
    }

    public static long getObjectSize(Object o) {
        return instrumentation.getObjectSize(o);
    }
}

Ajoutez ce qui suit à votre MANIFEST.MF:

Premain-Class: ObjectSizeFetcher

Utilisez getObjectSize :

public class C {
    private int x;
    private int y;

    public static void main(String [] args) {
        System.out.println(ObjectSizeFetcher.getObjectSize(new C()));
    }
}

Invoquer avec :

java -javaagent:ObjectSizeFetcherAgent.jar C

Autres conseils

Tu devrais utiliser joyeux, un outil développé dans le cadre du projet OpenJDK.

JOL (Java Object Layout) est la petite boîte à outils permettant d'analyser les schémas de disposition des objets dans les JVM.Ces outils utilisent largement Unsafe, JVMTI et Serviceability Agent (SA) pour décoder la disposition, l'empreinte et les références réelles des objets.Cela rend JOL beaucoup plus précis que d'autres outils reposant sur des vidages de tas, des hypothèses de spécification, etc.

Pour obtenir les tailles des primitives, des références et des éléments du tableau, utilisez VMSupport.vmDetails().Sur Oracle JDK 1.8.0_40 exécuté sur Windows 64 bits (utilisé pour tous les exemples suivants), cette méthode renvoie

Running 64-bit HotSpot VM.
Using compressed oop with 0-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]

Vous pouvez obtenir la taille réduite d'une instance d'objet en utilisant ClassLayout.parseClass(Foo.class).toPrintable() (en passant éventuellement une instance à toPrintable).Il s'agit uniquement de l'espace consommé par une seule instance de cette classe ;il n'inclut aucun autre objet référencé par cette classe.Il fait inclure la surcharge de la VM pour l'en-tête de l'objet, l'alignement des champs et le remplissage.Pour java.util.regex.Pattern:

java.util.regex.Pattern object internals:
 OFFSET  SIZE        TYPE DESCRIPTION                    VALUE
      0     4             (object header)                01 00 00 00 (0000 0001 0000 0000 0000 0000 0000 0000)
      4     4             (object header)                00 00 00 00 (0000 0000 0000 0000 0000 0000 0000 0000)
      8     4             (object header)                cb cf 00 20 (1100 1011 1100 1111 0000 0000 0010 0000)
     12     4         int Pattern.flags                  0
     16     4         int Pattern.capturingGroupCount    1
     20     4         int Pattern.localCount             0
     24     4         int Pattern.cursor                 48
     28     4         int Pattern.patternLength          0
     32     1     boolean Pattern.compiled               true
     33     1     boolean Pattern.hasSupplementary       false
     34     2             (alignment/padding gap)        N/A
     36     4      String Pattern.pattern                (object)
     40     4      String Pattern.normalizedPattern      (object)
     44     4        Node Pattern.root                   (object)
     48     4        Node Pattern.matchRoot              (object)
     52     4       int[] Pattern.buffer                 null
     56     4         Map Pattern.namedGroups            null
     60     4 GroupHead[] Pattern.groupNodes             null
     64     4       int[] Pattern.temp                   null
     68     4             (loss due to the next object alignment)
Instance size: 72 bytes (reported by Instrumentation API)
Space losses: 2 bytes internal + 4 bytes external = 6 bytes total

Vous pouvez obtenir une vue récapitulative de la taille profonde d'une instance d'objet en utilisant GraphLayout.parseInstance(obj).toFootprint().Bien sûr, certains objets de l'empreinte peuvent être partagés (également référencés à partir d'autres objets), il s'agit donc d'une surapproximation de l'espace qui pourrait être récupéré lorsque cet objet est récupéré.Pour le résultat de Pattern.compile("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$") (pris à partir de cette réponse), jol rapporte une empreinte totale de 1 840 octets, dont seulement 72 sont l'instance Pattern elle-même.

java.util.regex.Pattern instance footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1       112       112   [C
         3       272       816   [Z
         1        24        24   java.lang.String
         1        72        72   java.util.regex.Pattern
         9        24       216   java.util.regex.Pattern$1
        13        24       312   java.util.regex.Pattern$5
         1        16        16   java.util.regex.Pattern$Begin
         3        24        72   java.util.regex.Pattern$BitClass
         3        32        96   java.util.regex.Pattern$Curly
         1        24        24   java.util.regex.Pattern$Dollar
         1        16        16   java.util.regex.Pattern$LastNode
         1        16        16   java.util.regex.Pattern$Node
         2        24        48   java.util.regex.Pattern$Single
        40                1840   (total)

Si vous utilisez plutôt GraphLayout.parseInstance(obj).toPrintable(), jol vous indiquera l'adresse, la taille, le type, la valeur et le chemin des déréférences de champ vers chaque objet référencé, bien que ce soit généralement trop de détails pour être utile.Pour l’exemple de modèle en cours, vous pourriez obtenir ce qui suit.(Les adresses changeront probablement entre les exécutions.)

java.util.regex.Pattern object externals:
          ADDRESS       SIZE TYPE                             PATH                           VALUE
         d5e5f290         16 java.util.regex.Pattern$Node     .root.next.atom.next           (object)
         d5e5f2a0        120 (something else)                 (somewhere else)               (something else)
         d5e5f318         16 java.util.regex.Pattern$LastNode .root.next.next.next.next.next.next.next (object)
         d5e5f328      21664 (something else)                 (somewhere else)               (something else)
         d5e647c8         24 java.lang.String                 .pattern                       (object)
         d5e647e0        112 [C                               .pattern.value                 [^, [, a, -, z, A, -, Z, 0, -, 9, _, ., +, -, ], +, @, [, a, -, z, A, -, Z, 0, -, 9, -, ], +, \, ., [, a, -, z, A, -, Z, 0, -, 9, -, ., ], +, $]
         d5e64850        448 (something else)                 (somewhere else)               (something else)
         d5e64a10         72 java.util.regex.Pattern                                         (object)
         d5e64a58        416 (something else)                 (somewhere else)               (something else)
         d5e64bf8         16 java.util.regex.Pattern$Begin    .root                          (object)
         d5e64c08         24 java.util.regex.Pattern$BitClass .root.next.atom.val$rhs        (object)
         d5e64c20        272 [Z                               .root.next.atom.val$rhs.bits   [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e64d30         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64d48         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e64d60         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64d78         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e64d90         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64da8         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e64dc0         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs (object)
         d5e64dd8         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs        (object)
         d5e64df0         24 java.util.regex.Pattern$5        .root.next.atom                (object)
         d5e64e08         32 java.util.regex.Pattern$Curly    .root.next                     (object)
         d5e64e28         24 java.util.regex.Pattern$Single   .root.next.next                (object)
         d5e64e40         24 java.util.regex.Pattern$BitClass .root.next.next.next.atom.val$rhs (object)
         d5e64e58        272 [Z                               .root.next.next.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e64f68         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e64f80         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$lhs.val$rhs (object)
         d5e64f98         24 java.util.regex.Pattern$5        .root.next.next.next.atom.val$lhs.val$lhs (object)
         d5e64fb0         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$rhs (object)
         d5e64fc8         24 java.util.regex.Pattern$5        .root.next.next.next.atom.val$lhs (object)
         d5e64fe0         24 java.util.regex.Pattern$5        .root.next.next.next.atom      (object)
         d5e64ff8         32 java.util.regex.Pattern$Curly    .root.next.next.next           (object)
         d5e65018         24 java.util.regex.Pattern$Single   .root.next.next.next.next      (object)
         d5e65030         24 java.util.regex.Pattern$BitClass .root.next.next.next.next.next.atom.val$rhs (object)
         d5e65048        272 [Z                               .root.next.next.next.next.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e65158         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e65170         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e65188         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e651a0         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$rhs (object)
         d5e651b8         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs.val$lhs (object)
         d5e651d0         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs (object)
         d5e651e8         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom (object)
         d5e65200         32 java.util.regex.Pattern$Curly    .root.next.next.next.next.next (object)
         d5e65220        120 (something else)                 (somewhere else)               (something else)
         d5e65298         24 java.util.regex.Pattern$Dollar   .root.next.next.next.next.next.next (object)

Les entrées "(autre chose)" décrire d'autres objets dans le tas qui ne font pas partie de ce graphe d'objets.

La meilleure documentation amusante est la échantillons sympas dans le dépôt jol.Les exemples illustrent les opérations jol courantes et montrent comment vous pouvez utiliser jol pour analyser les composants internes de la VM et du garbage collector.

Il y a quelques années, Javaworld avait un article sur la détermination de la taille des objets Java composites et potentiellement imbriqués, ils expliquent essentiellement la création d'une implémentation sizeof() en Java.L'approche s'appuie essentiellement sur d'autres travaux dans lesquels des personnes ont identifié expérimentalement la taille des primitives et des objets Java typiques, puis ont appliqué ces connaissances à une méthode qui parcourt de manière récursive un graphe d'objets pour calculer la taille totale.

Cela sera toujours un peu moins précis qu'une implémentation native en C simplement à cause des choses qui se passent dans les coulisses d'une classe, mais cela devrait être un bon indicateur.

Alternativement, un projet SourceForge appelé à juste titre taille de qui propose une bibliothèque Java5 avec une implémentation sizeof().

P.S.N'utilisez pas l'approche de sérialisation, il n'y a aucune corrélation entre la taille d'un objet sérialisé et la quantité de mémoire qu'il consomme lorsqu'il est actif.

Premièrement, « la taille d'un objet » n'est pas un concept bien défini en Java.Vous pourriez désigner l'objet lui-même, avec uniquement ses membres, l'objet et tous les objets auxquels il fait référence (le graphe de référence).Vous pouvez parler de la taille en mémoire ou de la taille sur le disque.Et la JVM est autorisée à optimiser des choses comme les chaînes.

Donc la seule bonne façon est de demander à la JVM, avec un bon profileur (j'utilise VotreKit), ce qui n'est probablement pas ce que vous souhaitez.

Cependant, d'après la description ci-dessus, il semble que chaque ligne sera autonome et n'aura pas de grande arborescence de dépendances, donc la méthode de sérialisation sera probablement une bonne approximation sur la plupart des JVM.La façon la plus simple de procéder est la suivante :

 Serializable ser;
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(baos);
 oos.writeObject(ser);
 oos.close();
 return baos.size();

N'oubliez pas que si vous avez des objets avec des références communes, ceci Ne fera pas donne le résultat correct, et la taille de la sérialisation ne correspondra pas toujours à la taille en mémoire, mais c'est une bonne approximation.Le code sera un peu plus efficace si vous initialisez la taille de ByteArrayOutputStream à une valeur raisonnable.

J'ai accidentellement trouvé une classe Java "jdk.nashorn.internal.ir.debug.objectsizcalculator", déjà dans JDK, qui est facile à utiliser et semble très utile pour déterminer la taille d'un objet.

System.out.println(ObjectSizeCalculator.getObjectSize(new gnu.trove.map.hash.TObjectIntHashMap<String>(12000, 0.6f, -1)));
System.out.println(ObjectSizeCalculator.getObjectSize(new HashMap<String, Integer>(100000)));
System.out.println(ObjectSizeCalculator.getObjectSize(3));
System.out.println(ObjectSizeCalculator.getObjectSize(new int[]{1, 2, 3, 4, 5, 6, 7 }));
System.out.println(ObjectSizeCalculator.getObjectSize(new int[100]));

résultats:

164192
48
16
48
416

Si vous souhaitez simplement savoir combien de mémoire est utilisée dans votre JVM et quelle quantité est libre, vous pouvez essayer quelque chose comme ceci :

// Get current size of heap in bytes
long heapSize = Runtime.getRuntime().totalMemory();

// Get maximum size of heap in bytes. The heap cannot grow beyond this size.
// Any attempt will result in an OutOfMemoryException.
long heapMaxSize = Runtime.getRuntime().maxMemory();

// Get amount of free memory within the heap in bytes. This size will increase
// after garbage collection and decrease as new objects are created.
long heapFreeSize = Runtime.getRuntime().freeMemory();

modifier:J'ai pensé que cela pourrait être utile, car l'auteur de la question a également déclaré qu'il aimerait avoir une logique capable de "lire autant de lignes que possible jusqu'à ce que j'utilise 32 Mo de mémoire".

À l'époque où je travaillais sur Twitter, j'ai écrit un utilitaire permettant de calculer la taille profonde des objets.Il prend en compte différents modèles de mémoire (32 bits, oups compressés, 64 bits), le remplissage, le remplissage de sous-classe, fonctionne correctement sur les structures de données circulaires et les tableaux.Vous pouvez simplement compiler ce fichier .java ;il n'a pas de dépendances externes :

https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/objectsize/ObjectSizeCalculator.java

La plupart des autres réponses fournissent des tailles peu profondes - par ex.la taille d'un HashMap sans aucune clé ou valeur, ce qui n'est probablement pas ce que vous souhaitez.

Le projet jamm utilise le package java.lang.instrumentation ci-dessus mais parcourt l'arborescence et peut ainsi vous permettre d'utiliser la mémoire en profondeur.

new MemoryMeter().measureDeep(myHashMap);

https://github.com/jbellis/jamm

Pour utiliser MemoryMeter, démarrez la JVM avec "-javaagent:/jamm.jar"

Vous devez parcourir les objets en utilisant la réflexion.Soyez prudent lorsque vous le faites :

  • Le simple fait d'allouer un objet entraîne une certaine surcharge dans la JVM.Le montant varie selon la JVM, vous pouvez donc faire de cette valeur un paramètre.Faites-en au moins une constante (8 octets ?) et appliquez-la à tout ce qui est alloué.
  • Juste parce que byte est théoriquement de 1 octet ne signifie pas qu'il n'en faut qu'un en mémoire.
  • Il y aura des boucles dans les références d'objets, vous devrez donc conserver un HashMap ou quelque chose comme ça en utilisant des objets égaux comme comparateur pour éliminer les boucles infinies.

@jodonnell :J'aime la simplicité de votre solution, mais de nombreux objets ne sont pas sérialisables (cela générerait donc une exception), les champs peuvent être transitoires et les objets peuvent remplacer les méthodes standard.

Vous devez le mesurer avec un outil, ou l'estimer à la main, et cela dépend de la JVM que vous utilisez.

Il y a une surcharge fixe par objet.C'est spécifique à la JVM, mais j'estime généralement 40 octets.Ensuite, il faut regarder les membres de la classe.Les références d'objet sont de 4 (8) octets dans une JVM 32 bits (64 bits).Les types primitifs sont :

  • booléen et octet :1 octet
  • char et court :2 octets
  • int et flottant :4 octets
  • long et double :8 octets

Les tableaux suivent les mêmes règles ;c'est-à-dire qu'il s'agit d'une référence d'objet qui prend donc 4 (ou 8) octets dans votre objet, puis sa longueur multipliée par la taille de son élément.

Essayer de le faire par programme avec des appels à Runtime.freeMemory() ne vous donne tout simplement pas beaucoup de précision, à cause des appels asynchrones au garbage collector, etc.Le profilage du tas avec -Xrunhprof ou d'autres outils vous donnera les résultats les plus précis.

Le java.lang.instrument.Instrumentation La classe fournit un bon moyen d'obtenir la taille d'un objet Java, mais elle nécessite que vous définissiez un premain et exécutez votre programme avec un agent Java.C'est très ennuyeux lorsque vous n'avez besoin d'aucun agent et que vous devez ensuite fournir un agent Jar factice à votre application.

J'ai donc eu une solution alternative en utilisant le Unsafe classe de la sun.misc.Ainsi, en considérant l'alignement du tas des objets en fonction de l'architecture du processeur et en calculant le décalage de champ maximum, vous pouvez mesurer la taille d'un objet Java.Dans l'exemple ci-dessous, j'utilise une classe auxiliaire UtilUnsafe pour obtenir une référence à sun.misc.Unsafe objet.

private static final int NR_BITS = Integer.valueOf(System.getProperty("sun.arch.data.model"));
private static final int BYTE = 8;
private static final int WORD = NR_BITS/BYTE;
private static final int MIN_SIZE = 16; 

public static int sizeOf(Class src){
    //
    // Get the instance fields of src class
    // 
    List<Field> instanceFields = new LinkedList<Field>();
    do{
        if(src == Object.class) return MIN_SIZE;
        for (Field f : src.getDeclaredFields()) {
            if((f.getModifiers() & Modifier.STATIC) == 0){
                instanceFields.add(f);
            }
        }
        src = src.getSuperclass();
    }while(instanceFields.isEmpty());
    //
    // Get the field with the maximum offset
    //  
    long maxOffset = 0;
    for (Field f : instanceFields) {
        long offset = UtilUnsafe.UNSAFE.objectFieldOffset(f);
        if(offset > maxOffset) maxOffset = offset; 
    }
    return  (((int)maxOffset/WORD) + 1)*WORD; 
}
class UtilUnsafe {
    public static final sun.misc.Unsafe UNSAFE;

    static {
        Object theUnsafe = null;
        Exception exception = null;
        try {
            Class<?> uc = Class.forName("sun.misc.Unsafe");
            Field f = uc.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            theUnsafe = f.get(uc);
        } catch (Exception e) { exception = e; }
        UNSAFE = (sun.misc.Unsafe) theUnsafe;
        if (UNSAFE == null) throw new Error("Could not obtain access to sun.misc.Unsafe", exception);
    }
    private UtilUnsafe() { }
}

Il y a aussi le Mesureur de mémoire outil (anciennement chez Code Google, maintenant GitHub), qui est simple et publié sous le nom commercial Licence Apache 2.0, comme discuté dans un question similaire.

Cela nécessite également un argument de ligne de commande pour l'interpréteur Java si vous souhaitez mesurer la consommation d'octets de mémoire, mais sinon cela semble très bien fonctionner, du moins dans les scénarios que je l'ai utilisés.

Voici un utilitaire que j'ai créé en utilisant certains des exemples liés pour gérer 32 bits, 64 bits et 64 bits avec une POO compressée.Il utilise sun.misc.Unsafe.

Il utilise Unsafe.addressSize() pour obtenir la taille d'un pointeur natif et Unsafe.arrayIndexScale( Object[].class ) pour la taille d'une référence Java.

Il utilise le décalage de champ d'une classe connue pour déterminer la taille de base d'un objet.

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Stack;
import sun.misc.Unsafe;

/** Usage: 
 * MemoryUtil.sizeOf( object )
 * MemoryUtil.deepSizeOf( object )
 * MemoryUtil.ADDRESS_MODE
 */
public class MemoryUtil
{
    private MemoryUtil()
    {
    }

    public static enum AddressMode
    {
        /** Unknown address mode. Size calculations may be unreliable. */
        UNKNOWN,
        /** 32-bit address mode using 32-bit references. */
        MEM_32BIT,
        /** 64-bit address mode using 64-bit references. */
        MEM_64BIT,
        /** 64-bit address mode using 32-bit compressed references. */
        MEM_64BIT_COMPRESSED_OOPS
    }

    /** The detected runtime address mode. */
    public static final AddressMode ADDRESS_MODE;

    private static final Unsafe UNSAFE;

    private static final long ADDRESS_SIZE; // The size in bytes of a native pointer: 4 for 32 bit, 8 for 64 bit
    private static final long REFERENCE_SIZE; // The size of a Java reference: 4 for 32 bit, 4 for 64 bit compressed oops, 8 for 64 bit
    private static final long OBJECT_BASE_SIZE; // The minimum size of an Object: 8 for 32 bit, 12 for 64 bit compressed oops, 16 for 64 bit
    private static final long OBJECT_ALIGNMENT = 8;

    /** Use the offset of a known field to determine the minimum size of an object. */
    private static final Object HELPER_OBJECT = new Object() { byte b; };


    static
    {
        try
        {
            // Use reflection to get a reference to the 'Unsafe' object.
            Field f = Unsafe.class.getDeclaredField( "theUnsafe" );
            f.setAccessible( true );
            UNSAFE = (Unsafe) f.get( null );

            OBJECT_BASE_SIZE = UNSAFE.objectFieldOffset( HELPER_OBJECT.getClass().getDeclaredField( "b" ) );

            ADDRESS_SIZE = UNSAFE.addressSize();
            REFERENCE_SIZE = UNSAFE.arrayIndexScale( Object[].class );

            if( ADDRESS_SIZE == 4 )
            {
                ADDRESS_MODE = AddressMode.MEM_32BIT;
            }
            else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 8 )
            {
                ADDRESS_MODE = AddressMode.MEM_64BIT;
            }
            else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 4 )
            {
                ADDRESS_MODE = AddressMode.MEM_64BIT_COMPRESSED_OOPS;
            }
            else
            {
                ADDRESS_MODE = AddressMode.UNKNOWN;
            }
        }
        catch( Exception e )
        {
            throw new Error( e );
        }
    }


    /** Return the size of the object excluding any referenced objects. */
    public static long shallowSizeOf( final Object object )
    {
        Class<?> objectClass = object.getClass();
        if( objectClass.isArray() )
        {
            // Array size is base offset + length * element size
            long size = UNSAFE.arrayBaseOffset( objectClass )
                    + UNSAFE.arrayIndexScale( objectClass ) * Array.getLength( object );
            return padSize( size );
        }
        else
        {
            // Object size is the largest field offset padded out to 8 bytes
            long size = OBJECT_BASE_SIZE;
            do
            {
                for( Field field : objectClass.getDeclaredFields() )
                {
                    if( (field.getModifiers() & Modifier.STATIC) == 0 )
                    {
                        long offset = UNSAFE.objectFieldOffset( field );
                        if( offset >= size )
                        {
                            size = offset + 1; // Field size is between 1 and PAD_SIZE bytes. Padding will round up to padding size.
                        }
                    }
                }
                objectClass = objectClass.getSuperclass();
            }
            while( objectClass != null );

            return padSize( size );
        }
    }


    private static final long padSize( final long size )
    {
        return (size + (OBJECT_ALIGNMENT - 1)) & ~(OBJECT_ALIGNMENT - 1);
    }


    /** Return the size of the object including any referenced objects. */
    public static long deepSizeOf( final Object object )
    {
        IdentityHashMap<Object,Object> visited = new IdentityHashMap<Object,Object>();
        Stack<Object> stack = new Stack<Object>();
        if( object != null ) stack.push( object );

        long size = 0;
        while( !stack.isEmpty() )
        {
            size += internalSizeOf( stack.pop(), stack, visited );
        }
        return size;
    }


    private static long internalSizeOf( final Object object, final Stack<Object> stack, final IdentityHashMap<Object,Object> visited )
    {
        // Scan for object references and add to stack
        Class<?> c = object.getClass();
        if( c.isArray() && !c.getComponentType().isPrimitive() )
        {
            // Add unseen array elements to stack
            for( int i = Array.getLength( object ) - 1; i >= 0; i-- )
            {
                Object val = Array.get( object, i );
                if( val != null && visited.put( val, val ) == null )
                {
                    stack.add( val );
                }
            }
        }
        else
        {
            // Add unseen object references to the stack
            for( ; c != null; c = c.getSuperclass() )
            {
                for( Field field : c.getDeclaredFields() )
                {
                    if( (field.getModifiers() & Modifier.STATIC) == 0 
                            && !field.getType().isPrimitive() )
                    {
                        field.setAccessible( true );
                        try
                        {
                            Object val = field.get( object );
                            if( val != null && visited.put( val, val ) == null )
                            {
                                stack.add( val );
                            }
                        }
                        catch( IllegalArgumentException e )
                        {
                            throw new RuntimeException( e );
                        }
                        catch( IllegalAccessException e )
                        {
                            throw new RuntimeException( e );
                        }
                    }
                }
            }
        }

        return shallowSizeOf( object );
    }
}

Sans avoir à vous soucier de l'instrumentation, etc., et si vous n'avez pas besoin de connaître la taille exacte en octets d'un objet, vous pouvez utiliser l'approche suivante :

System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

do your job here

System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

De cette façon, vous lisez la mémoire utilisée avant et après, et en appelant le GC juste avant d'obtenir la mémoire utilisée, vous réduisez le "bruit" presque à 0.

Pour un résultat plus fiable, vous pouvez exécuter votre travail n fois, puis diviser la mémoire utilisée par n, obtenant ainsi la quantité de mémoire nécessaire à une exécution.De plus, vous pouvez tout exécuter plusieurs fois et faire une moyenne.

Il n’y a pas d’appel de méthode, si c’est ce que vous demandez.Avec un peu de recherche, je suppose que vous pourriez écrire le vôtre.Une instance particulière a une taille fixe dérivée du nombre de références et de valeurs primitives ainsi que des données de comptabilité de l'instance.Vous parcourriez simplement le graphe d’objets.Moins les types de lignes sont variés, plus c'est facile.

Si c'est trop lent ou si cela pose simplement plus de problèmes que cela n'en vaut la peine, il existe toujours une bonne vieille règle empirique de comptage de lignes.

J'ai écrit une fois un test rapide pour estimer à la volée :

public class Test1 {

    // non-static nested
    class Nested { }

    // static nested
    static class StaticNested { }

    static long getFreeMemory () {
        // waits for free memory measurement to stabilize
        long init = Runtime.getRuntime().freeMemory(), init2;
        int count = 0;
        do {
            System.out.println("waiting..." + init);
            System.gc();
            try { Thread.sleep(250); } catch (Exception x) { }
            init2 = init;
            init = Runtime.getRuntime().freeMemory();
            if (init == init2) ++ count; else count = 0;
        } while (count < 5);
        System.out.println("ok..." + init);
        return init;
    }

    Test1 () throws InterruptedException {

        Object[] s = new Object[10000];
        Object[] n = new Object[10000];
        Object[] t = new Object[10000];

        long init = getFreeMemory();

        //for (int j = 0; j < 10000; ++ j)
        //    s[j] = new Separate();

        long afters = getFreeMemory();

        for (int j = 0; j < 10000; ++ j)
            n[j] = new Nested();

        long aftersn = getFreeMemory();

        for (int j = 0; j < 10000; ++ j)
            t[j] = new StaticNested();

        long aftersnt = getFreeMemory();

        System.out.println("separate:      " + -(afters - init) + " each=" + -(afters - init) / 10000);
        System.out.println("nested:        " + -(aftersn - afters) + " each=" + -(aftersn - afters) / 10000);
        System.out.println("static nested: " + -(aftersnt - aftersn) + " each=" + -(aftersnt - aftersn) / 10000);

    }

    public static void main (String[] args) throws InterruptedException {
        new Test1();
    }

}

Le concept général est d'allouer des objets et de mesurer les changements dans l'espace libre du tas.La clé étant getFreeMemory(), lequel demande l'exécution du GC et attend que la taille du tas libre signalée se stabilise.Le résultat de ce qui précède est :

nested:        160000 each=16
static nested: 160000 each=16

C'est ce à quoi nous nous attendons, compte tenu du comportement d'alignement et de l'éventuelle surcharge d'en-tête de bloc de tas.

La méthode d'instrumentation détaillée dans la réponse acceptée est ici la plus précise.La méthode que j'ai décrite est précise mais uniquement dans des conditions contrôlées où aucun autre thread ne crée/supprime des objets.

Utilisez simplement Java Visual VM.

Il contient tout ce dont vous avez besoin pour profiler et déboguer les problèmes de mémoire.

Il dispose également d'une console OQL (Object Query Language) qui vous permet de faire de nombreuses choses utiles, dont l'une étant sizeof(o)

Ma réponse est basée sur le code fourni par Nick.Ce code mesure la quantité totale d'octets occupés par l'objet sérialisé.Donc, cela mesure en fait les éléments de sérialisation + l'empreinte mémoire des objets simples (il suffit de sérialiser par exemple int et vous verrez que la quantité totale d'octets sérialisés n'est pas 4).Donc, si vous souhaitez que le numéro d'octet brut soit utilisé exactement pour votre objet, vous devez modifier un peu ce code.Ainsi:

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectSizeCalculator {
    private Object getFirstObjectReference(Object o) {
        String objectType = o.getClass().getTypeName();

        if (objectType.substring(objectType.length()-2).equals("[]")) {
            try {
                if (objectType.equals("java.lang.Object[]"))
                    return ((Object[])o)[0];
                else if (objectType.equals("int[]"))
                    return ((int[])o)[0];
                else
                    throw new RuntimeException("Not Implemented !");
            } catch (IndexOutOfBoundsException e) {
                return null;
            }
        }

        return o;
    } 

    public int getObjectSizeInBytes(Object o) {
        final String STRING_JAVA_TYPE_NAME = "java.lang.String";

        if (o == null)
            return 0;

        String objectType = o.getClass().getTypeName();
        boolean isArray = objectType.substring(objectType.length()-2).equals("[]");

        Object objRef = getFirstObjectReference(o);
        if (objRef != null && !(objRef instanceof Serializable))
            throw new RuntimeException("Object must be serializable for measuring it's memory footprint using this method !");

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(o);
            oos.close();
            byte[] bytes = baos.toByteArray();

            for (int i = bytes.length - 1, j = 0; i != 0; i--, j++) {
                if (objectType != STRING_JAVA_TYPE_NAME) {
                    if (bytes[i] == 112)
                        if (isArray)
                            return j - 4;
                        else
                            return j;
                } else {
                    if (bytes[i] == 0)
                        return j - 1;
                }
            }
        } catch (Exception e) {
            return -1;
        }

        return -1;
    }    

}

J'ai testé cette solution avec des types primitifs, String et certaines classes triviales.Il se peut également qu’il y ait des cas non couverts.


MISE À JOUR: Exemple modifié pour prendre en charge le calcul de l'empreinte mémoire des objets du tableau.

Vous pouvez générer un vidage de tas (avec jmap, par exemple), puis analyser la sortie pour trouver la taille des objets.Il s'agit d'une solution hors ligne, mais vous pouvez examiner les tailles peu profondes et profondes, etc.

long heapSizeBefore = Runtime.getRuntime().totalMemory();

// Code for object construction
...
long heapSizeAfter = Runtime.getRuntime().totalMemory();
long size = heapSizeAfter - heapSizeBefore;

size vous donne l'augmentation de l'utilisation de la mémoire du jvm en raison de la création d'objets et il s'agit généralement de la taille de l'objet.

Cette réponse n'est pas liée à la taille de l'objet, mais lorsque vous utilisez un tableau pour accueillir les objets ;la taille de mémoire qu'il allouera à l'objet.

Ainsi, les tableaux, listes ou cartes de toutes ces collections ne stockeront pas vraiment les objets (uniquement au moment des primitives, la taille réelle de la mémoire des objets est nécessaire), elles stockeront uniquement les références pour ces objets.

Maintenant le Used heap memory = sizeOfObj + sizeOfRef (* 4 bytes) in collection

  • (4/8 octets) dépend du système d'exploitation (32/64 bits)

PRIMITIVES

int   [] intArray    = new int   [1]; will require 4 bytes.
long  [] longArray   = new long  [1]; will require 8 bytes.

OBJETS

Object[] objectArray = new Object[1]; will require 4 bytes. The object can be any user defined Object.
Long  [] longArray   = new Long  [1]; will require 4 bytes.

Je veux dire que tout l'objet REFERENCE n'a besoin que de 4 octets de mémoire.Il peut s'agir d'une référence de chaîne OU d'une référence d'objet double, mais cela dépend de la création de l'objet, la mémoire nécessaire variera.

par exemple) Si je crée un objet pour la classe ci-dessous ReferenceMemoryTest alors 4 + 4 + 4 = 12 octets de mémoire seront créés.La mémoire peut différer lorsque vous essayez d'initialiser les références.

 class ReferenceMemoryTest {
    public String refStr;
    public Object refObj;
    public Double refDoub; 
}

Ainsi, lors de la création d’un tableau d’objets/références, tout son contenu sera occupé par des références NULL.Et nous savons que chaque référence nécessite 4 octets.

Et enfin, l'allocation de mémoire pour le code ci-dessous est de 20 octets.

ReferenceMemoryTest ref1 = new ReferenceMemoryTest();(4 (REF1) + 12 = 16 octets) RefeCoMoryTest REF2 = REF1;( 4(réf2) + 16 = 20 octets)

Supposons que je déclare une classe nommée Complex comme:

public class Complex {

    private final long real;
    private final long imaginary;

    // omitted
}

Afin de voir combien de mémoire est allouée aux instances actives de cette classe :

$ jmap -histo:live <pid> | grep Complex

 num     #instances         #bytes  class name (module)
-------------------------------------------------------
 327:             1             32  Complex

Pour JSONObject, le code ci-dessous peut vous aider.

`JSONObject.toString().getBytes("UTF-8").length`

renvoie la taille en octets

Je l'ai vérifié avec mon objet JSONArray en l'écrivant dans un fichier.Cela donne la taille de l'objet.

Je doute que vous souhaitiez le faire par programme, à moins que vous ne souhaitiez le faire une fois et le stocker pour une utilisation future.C'est une chose coûteuse à faire.Il n'y a pas d'opérateur sizeof() en Java, et même s'il y en avait, il ne compterait que le coût des références à d'autres objets et la taille des primitives.

Une façon de procéder consiste à sérialiser l'élément dans un fichier et à examiner la taille du fichier, comme ceci :

Serializable myObject;
ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("obj.ser"));
oos.write (myObject);
oos.close ();

Bien entendu, cela suppose que chaque objet est distinct et ne contient aucune référence non transitoire à autre chose.

Une autre stratégie consisterait à prendre chaque objet et à examiner ses membres par réflexion et à additionner les tailles (boolean & byte = 1 octet, short & char = 2 octets, etc.), en descendant dans la hiérarchie des membres.Mais c'est fastidieux et coûteux et finit par faire la même chose que la stratégie de sérialisation.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top