2009-04-16 13 views
16

Necesito averiguar programáticamente cuánta memoria está ocupando un objeto Java dado, incluida la memoria ocupada por los objetos a los que hace referencia.Calcula programáticamente la memoria ocupada por un objeto Java, incluidos los objetos a los que hace referencia

Puedo generar un volcado de memoria dinámico y analizar los resultados usando una herramienta. Sin embargo, lleva un tiempo considerable generar un volcado dinámico y una herramienta de este tipo para leer el volcado y generar informes. Dado el hecho de que probablemente necesite hacer esto varias veces, mi trabajo sería mucho más ágil si pudiera incluir algún código en mi proyecto que me diera este valor de "tiempo de ejecución".

¿Cómo puedo lograrlo?

ps: Específicamente Tengo una colección de objetos de tipo javax.xml.transform.Templates

+0

No lo llamaría una estafa exacta. – Inisheer

+0

Bueno, no es exactamente una tontería, ya que la pregunta original no proporcionó una respuesta utilizable en este contexto ... – Varkhan

+0

Proporcionó varios. – erickson

Respuesta

11

Usted tendrá que utilizar la reflexión para eso. La pieza resultante de código es demasiado complicado para mí para publicar aquí (aunque pronto estará disponible como parte de un conjunto de herramientas GPL Estoy construyendo), pero la idea principal es:

  • Una cabecera del objeto utiliza 8 bytes (para puntero clase y recuento de referencia)
  • Cada campo primitiva utiliza 1, 2, 4 u 8 bytes en función del tipo real
  • Cada referencia de objeto (es decir, no primitivo) campo utiliza 4 bytes (la referencia, además de cualquiera que sea el uso de objeto referenciado)

Necesita tratar las matrices por separado (8 por tes de encabezado, 4 bytes de campo de longitud, 4 bytes de longitud de tabla, más lo que sea que utilicen los objetos dentro). Necesita procesar otros tipos de objetos iterando a través de los campos (y sus campos de padres) usando la reflexión.

También necesita mantener un conjunto de objetos "vistos" durante la recursión, para no contar varias veces los objetos a los que se hace referencia en varios lugares.

+2

Por lo que vale, el encabezado de objeto usa al menos eso, pero creo que las JVM antiguas usan más. Eso es fácil (suficiente) para verificarlo también, mediante un simple código de prueba (crear un millón de objetos, gc, dormir un poco, verificar la memoria libre -no teóricamente garantiza que funcione, en la práctica sí lo hace). – StaxMan

+2

También: 4 bytes es para referencias de 32 bits. Para sistemas de 64 bits, 8 bytes, creo. – StaxMan

+0

Bueno, yo también estaba pensando eso por un tiempo, pero no pude encontrar ninguna referencia sobre eso ... así que probé algunas evaluaciones comparativas como StaxMan describió, y parece que todavía son 4 ... vaya figura. – Varkhan

3

Una buena solución genérica es usar delta de tamaño de pila. Esto implica un esfuerzo mínimo y es reutilizable entre cualquier tipo de gráfico objeto/objeto. Al crear instancias y destruir sus objetos muchas veces y recolectar basura entre ellos, y luego tomar el promedio, evita las optimizaciones de compilación y JVM que alteran los resultados y obtienen un resultado bastante preciso. Si necesita una respuesta EXACTA hasta el byte, puede que esta no sea la solución para usted, pero para todas las aplicaciones prácticas que conozco (creación de perfiles, cálculos de requisitos de memoria) funciona extremadamente bien. El siguiente código hará exactamente eso.

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

Esto está dañado en virtud del hecho de que los métodos gc() y runFinalization() no son deterministas. Es decir: son solo sugerencias para el tiempo de ejecución para hacer esas acciones. Es perfectamente legal para un tiempo de ejecución ignorarlos. –

+1

no tiene fallas: a veces se ignorarán como dices, por lo que la gran cantidad de iteraciones y el promedio de los resultados. – Peter

+1

Al menos podría haber acreditado la fuente de este código, incluso si lo ha modificado un poco: http://www.javaworld.com/javaworld/javatips/jw-javatip130.html – wolfcastle

6

Parece que ya hay una utilidad para hacer esto llama Classmexer.

No lo he probado yo solo, pero tomaría ese camino antes de hacer mi propia versión.

+1

+1 para el enlace. –

Cuestiones relacionadas