2009-04-16 4 views
16

J'ai besoin de connaître par programme exactement la quantité de mémoire occupée par un objet Java donné, y compris la mémoire occupée par les objets auxquels il fait référence.Calculer par programme la mémoire occupée par un objet Java y compris les objets qu'il référence

Je peux générer un vidage de mémoire et analyser les résultats à l'aide d'un outil. Cependant, il faut beaucoup de temps pour générer un vidage mémoire et pour qu'un tel outil lise la sauvegarde pour générer des rapports. Étant donné que j'aurai probablement besoin de faire cela plusieurs fois, mon travail serait beaucoup plus agile si je pouvais ajouter du code dans mon projet qui me donnerait cette valeur "runtime".

Comment pourrais-je le faire mieux?

ps: Plus précisément, j'ai une collection d'objets de type javax.xml.transform.Templates

+0

Je n'appellerais pas cela une dupe exacte. – Inisheer

+0

Eh bien, pas exactement dupe, comme la question initiale n'a pas réussi à fournir une réponse utilisable dans ce contexte ... – Varkhan

+0

Il a fourni plusieurs. – erickson

Répondre

11

Vous aurez besoin d'utiliser réflexion pour cela. La pièce résultante de code est trop compliqué pour moi de poster ici (bien qu'il sera bientôt disponible dans le cadre d'une boîte à outils GPL Je construis), mais l'idée principale est:

  • Un en-tête d'objet utilise 8 octets (
  • Chaque champ primitif utilise 1, 2, 4 ou 8 octets en fonction du type réel
  • Chaque champ de référence d'objet (c'est-à-dire non primitif) utilise 4 octets (la référence, plus utilisation de l'objet référencé)

Vous devez traiter les tableaux séparément (8 par tes d'en-tête, 4 octets de champ de longueur, 4 * octets de longueur de table, plus tout ce que les objets à l'intérieur utilisent). Vous devez traiter d'autres types d'objets en itérant à travers les champs (et les champs parents) en utilisant la réflexion.

Vous devez également conserver un ensemble d'objets "visibles" pendant la récursivité, afin de ne pas compter plusieurs fois les objets référencés à plusieurs endroits.

+2

Pour ce que ça vaut, l'en-tête d'objet utilise au moins autant, mais je pense que les anciennes JVM utilisent plus. C'est facile (assez) de vérifier, aussi, par un simple code de test (créer un million d'objets, gc, dormez un peu, consultez la mémoire libre - pas théoriquement garanti de travailler, dans la pratique). – StaxMan

+2

Aussi: 4 octets est pour les références 32 bits. Pour les systèmes 64 bits 8 octets, je pense. – StaxMan

+0

Eh bien, je pensais ça aussi pendant un moment, mais je n'ai trouvé aucune référence là-dessus ... alors j'ai essayé de faire un benchmarking comme StaxMan l'a décrit, et il semble que c'est encore 4 ... allez comprendre. – Varkhan

3

Une bonne solution générique consiste à utiliser un delta de taille de tas. Cela implique un effort minimal et est réutilisable entre n'importe quel type de graphe objet/objet. En instanciant et détruisant vos objets plusieurs fois et en collectant les ordures entre eux, puis en prenant la moyenne, vous évitez les optimisations du compilateur et de la JVM qui modifient les résultats et obtiennent un résultat assez précis. Si vous avez besoin d'une réponse EXACT à l'octet, alors cela peut ne pas être la solution pour vous, mais pour toutes les applications pratiques que je connais (profilage, calcul des besoins en mémoire) cela fonctionne extrêmement bien. Le code ci-dessous fera exactement cela.

public class Sizeof { 
     public static void main(String[] args) 
      throws Exception { 
     // "warm up" all classes/methods that we are going to use: 
     runGC(); 
     usedMemory(); 

     // array to keep strong references to allocated objects: 
     final int count = 10000; // 10000 or so is enough for small ojects 
     Object[] objects = new Object[count]; 

     long heap1 = 0; 

     // allocate count+1 objects, discard the first one: 
     for (int i = -1; i < count; ++i) { 
      Object object; 

    //// INSTANTIATE YOUR DATA HERE AND ASSIGN IT TO 'object': 


      object=YOUR OBJECT; 
    ////end your code here 
      if (i >= 0) { 
      objects[i] = object; 
      } 
      else { 
      object = null; // discard the "warmup" object 
      runGC(); 
      heap1 = usedMemory(); // take a "before" heap snapshot 
      } 
     } 

     runGC(); 
     long heap2 = usedMemory(); // take an "after" heap snapshot: 

     final int size = Math.round(((float)(heap2 - heap1))/count); 
     System.out.println("'before' heap: " + heap1 + 
          ", 'after' heap: " + heap2); 
     System.out.println("heap delta: " + (heap2 - heap1) + 
          ", {" + objects[0].getClass() + "} size = " + size + " bytes"); 
     } 

     // a helper method for creating Strings of desired length 
     // and avoiding getting tricked by String interning: 
     public static String createString(final int length) { 
     final char[] result = new char[length]; 
     for (int i = 0; i < length; ++i) { 
      result[i] = (char)i; 
     } 

     return new String(result); 
     } 

     // this is our way of requesting garbage collection to be run: 
     // [how aggressive it is depends on the JVM to a large degree, but 
     // it is almost always better than a single Runtime.gc() call] 
     private static void runGC() 
      throws Exception { 
     // for whatever reason it helps to call Runtime.gc() 
     // using several method calls: 
     for (int r = 0; r < 4; ++r) { 
      _runGC(); 
     } 
     } 

     private static void _runGC() 
      throws Exception { 
     long usedMem1 = usedMemory(), usedMem2 = Long.MAX_VALUE; 

     for (int i = 0; (usedMem1 < usedMem2) && (i < 1000); ++i) { 
      s_runtime.runFinalization(); 
      s_runtime.gc(); 
      Thread.currentThread().yield(); 

      usedMem2 = usedMem1; 
      usedMem1 = usedMemory(); 
     } 
     } 

     private static long usedMemory() { 
     return s_runtime.totalMemory() - s_runtime.freeMemory(); 
     } 

     private static final Runtime s_runtime = Runtime.getRuntime(); 

    } // end of class 
+1

Cette erreur est due au fait que les méthodes gc() et runFinalization() ne sont pas déterministes. I.e .: ils ne sont que des indications pour l'exécution de ces actions. Il est parfaitement légal pour une exécution de les ignorer. –

+1

il n'est pas vicié - ils seront parfois ignorés comme vous le dites, c'est pourquoi le grand nombre d'itérations et la moyenne des résultats. – Peter

+1

Vous pourriez avoir au moins crédité la source de ce code, même si vous l'avez un peu modifié: http://www.javaworld.com/javaworld/javatips/jw-javatip130.html – wolfcastle

6

On dirait qu'il est déjà un utilitaire pour ce faire appelé Classmexer.

Je ne l'ai pas essayé moi-même, mais j'irais dans cette voie avant de rouler la mienne.

+1

+1 pour le lien. –

Questions connexes