¿Cuál es el consumo de memoria de un objeto en Java?

216

¿El espacio de memoria consumido por un objeto con 100 atributos es el mismo que el de 100 objetos, con un atributo cada uno?

¿Cuánta memoria se asigna a un objeto?
¿Cuánto espacio adicional se usa al agregar un atributo?

keparo
fuente

Respuestas:

180

Mindprod señala que esta no es una pregunta fácil de responder:

Una JVM es libre de almacenar datos de la forma que desee internamente, con endian grande o pequeño, con cualquier cantidad de relleno o sobrecarga, aunque las primitivas deben comportarse como si tuvieran los tamaños oficiales.
Por ejemplo, la JVM o el compilador nativo podría decidir almacenar un boolean[]fragmento de 64 bits como a BitSet. No tiene que decírtelo, siempre que el programa dé las mismas respuestas.

  • Puede asignar algunos objetos temporales en la pila.
  • Puede optimizar algunas variables o llamadas a métodos totalmente fuera de existencia reemplazándolas por constantes.
  • Puede versionar métodos o bucles, es decir, compilar dos versiones de un método, cada una optimizada para una determinada situación, y luego decidir de antemano a cuál llamar.

Entonces, por supuesto, el hardware y el sistema operativo tienen cachés multicapa, en caché de chip, caché de SRAM, caché de DRAM, conjunto de trabajo de RAM normal y almacén de respaldo en el disco. Sus datos pueden estar duplicados en cada nivel de caché. Toda esta complejidad significa que solo puede predecir de manera aproximada el consumo de RAM.

Métodos de medición

Puede usar Instrumentation.getObjectSize()para obtener una estimación del almacenamiento consumido por un objeto.

Para visualizar el diseño real del objeto, la huella y las referencias, puede usar la herramienta JOL (Diseño de objetos Java) .

Encabezados de objetos y referencias de objetos

En un JDK moderno de 64 bits, un objeto tiene un encabezado de 12 bytes, rellenado con un múltiplo de 8 bytes, por lo que el tamaño mínimo del objeto es de 16 bytes. Para las JVM de 32 bits, la sobrecarga es de 8 bytes, acolchada a un múltiplo de 4 bytes. (A partir de la respuesta de Dmitry Spikhalskiy , la respuesta de Jayen y JavaWorld ).

Por lo general, las referencias son de 4 bytes en plataformas de 32 bits o en plataformas de 64 bits hasta -Xmx32G; y 8 bytes por encima de 32 Gb ( -Xmx32G). (Ver referencias de objetos comprimidos ).

Como resultado, una JVM de 64 bits normalmente requeriría un 30-50% más de espacio de almacenamiento dinámico. ( ¿Debo usar una JVM de 32 o 64 bits?, 2012, JDK 1.7)

Tipos en caja, matrices y cadenas

Las envolturas en caja tienen gastos generales en comparación con los tipos primitivos (de JavaWorld ):

  • Integer: El resultado de 16 bytes es un poco peor de lo que esperaba porque un intvalor puede caber en solo 4 bytes adicionales. El uso de Integerme cuesta una sobrecarga de memoria del 300 por ciento en comparación con cuando puedo almacenar el valor como un tipo primitivo

  • Long: 16 bytes también: claramente, el tamaño real del objeto en el montón está sujeto a una alineación de memoria de bajo nivel realizada por una implementación particular de JVM para un tipo de CPU en particular. Parece que a Longes 8 bytes de sobrecarga de objetos, más 8 bytes más para el valor largo real. En contraste, Integertenía un agujero de 4 bytes sin usar, muy probablemente porque la JVM que uso fuerza la alineación de objetos en un límite de palabra de 8 bytes.

Otros contenedores también son costosos:

  • Matrices multidimensionales : ofrece otra sorpresa.
    Los desarrolladores suelen emplear construcciones como int[dim1][dim2]en computación numérica y científica.

    En una int[dim1][dim2]instancia de matriz, cada int[dim2]matriz anidada es un Objecten sí misma. Cada uno agrega la sobrecarga habitual de la matriz de 16 bytes. Cuando no necesito una matriz triangular o irregular, eso representa pura sobrecarga. El impacto crece cuando las dimensiones de la matriz difieren mucho.

    Por ejemplo, una int[128][2]instancia ocupa 3.600 bytes. En comparación con los 1.040 bytes que int[256]usa una instancia (que tiene la misma capacidad), 3.600 bytes representan una sobrecarga del 246 por ciento. En el caso extremo de byte[256][1], el factor de gastos generales es casi 19! Compare eso con la situación de C / C ++ en la que la misma sintaxis no agrega ninguna sobrecarga de almacenamiento.

  • String: el Stringcrecimiento de la memoria de a sigue el crecimiento de su matriz de caracteres internos. sin embargo, elString clase agrega otros 24 bytes de sobrecarga.

    Para un Stringtamaño no vacío de 10 caracteres o menos, el costo indirecto agregado en relación con la carga útil (2 bytes por cada carácter más 4 bytes para la longitud), varía de 100 a 400 por ciento.

Alineación

Considere este objeto de ejemplo :

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
}

Una suma ingenua sugeriría que una instancia de Xusaría 17 bytes. Sin embargo, debido a la alineación (también llamada relleno), la JVM asigna la memoria en múltiplos de 8 bytes, por lo que en lugar de 17 bytes asignaría 24 bytes.

VonC
fuente
int [128] [6]: 128 matrices de 6 ints - 768 ints en total, 3072 bytes de datos + 2064 bytes Sobrecarga del objeto = 5166 bytes en total. int [256]: 256 ints en total, por lo tanto, no comparable. int [768]: 3072 bytes de datos + 16 byes de sobrecarga - aproximadamente 3/5 del espacio de la matriz 2D - ¡no del todo 246% de sobrecarga!
JeeBee
Ah, el artículo original usaba int [128] [2] no int [128] [6] - pregúntese cómo cambió eso. También muestra que los ejemplos extremos pueden contar una historia diferente.
JeeBee
2
La sobrecarga es de 16 bytes en JVM de 64 bits.
Tim Cooper el
3
@AlexWien: Algunos esquemas de recolección de basura pueden imponer un tamaño de objeto mínimo que es independiente del relleno. Durante la recolección de basura, una vez que un objeto se copia de una ubicación anterior a una nueva, es posible que la ubicación anterior ya no necesite mantener los datos del objeto, pero deberá contener una referencia a la nueva ubicación; también puede necesitar almacenar una referencia a la ubicación antigua del objeto en el que se descubrió la primera referencia y el desplazamiento de esa referencia dentro del objeto antiguo [ya que el objeto antiguo aún puede contener referencias que aún no se han procesado].
supercat
2
@AlexWien: el uso de la memoria en la ubicación anterior de un objeto para almacenar la información de la contabilidad del recolector de basura evita la necesidad de asignar otra memoria para ese propósito, pero puede imponer un tamaño de objeto mínimo que es más grande de lo que de otro modo sería necesario. Creo que al menos una versión del recolector de basura .NET usa ese enfoque; Ciertamente, también sería posible que algunos recolectores de basura de Java lo hicieran.
Supercat
34

Depende de la arquitectura / jdk. Para un JDK moderno y una arquitectura de 64 bits, un objeto tiene un encabezado de 12 bytes y un relleno de 8 bytes, por lo que el tamaño mínimo del objeto es de 16 bytes. Puedes usar una herramienta llamada Java Object Layout para determinar un tamaño y obtener detalles sobre el diseño del objeto y la estructura interna de cualquier entidad o adivinar esta información por referencia de clase. Ejemplo de una salida para Integer en mi entorno:

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

Entonces, para Integer, el tamaño de la instancia es de 16 bytes, porque 4 bytes int se compacta en su lugar justo después del encabezado y antes del límite de relleno.

Código de muestra:

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 usa maven, para obtener JOL:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.3.2</version>
</dependency>
Dmitry Spikhalskiy
fuente
28

Cada objeto tiene una cierta sobrecarga para su monitor asociado y la información de tipo, así como los propios campos. Más allá de eso, los campos se pueden establecer más o menos como la JVM lo considere apropiado (creo), pero como se muestra en otra respuesta , al menos algunas JVM se empacarán bastante. Considere una clase como esta:

public class SingleByte
{
    private byte b;
}

vs

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

En una JVM de 32 bits, esperaría que 100 instancias SingleBytetomaran 1200 bytes (8 bytes de sobrecarga + 4 bytes para el campo debido al relleno / alineación). Esperaría que una instancia OneHundredBytestomara 108 bytes: la sobrecarga y luego 100 bytes empaquetados. Sin embargo, puede variar según la JVM: una implementación puede decidir no empaquetar los campos enOneHundredBytes , lo que lleva a que tome 408 bytes (= 8 bytes por encima + 4 * 100 bytes alineados / rellenados). En una JVM de 64 bits, la sobrecarga también puede ser mayor (no estoy seguro).

EDITAR: Vea el comentario a continuación; aparentemente HotSpot se adapta a límites de 8 bytes en lugar de 32, por lo que cada instancia SingleBytetomaría 16 bytes.

De cualquier manera, el "objeto grande único" será al menos tan eficiente como múltiples objetos pequeños, para casos simples como este.

Jon Skeet
fuente
99
En realidad, una instancia de SingleByte tomaría 16 bytes en un Sun JVM, es decir, 8 bytes de sobrecarga, 4 bytes para el campo y luego 4 bytes para el relleno de objetos, ya que el compilador HotSpot redondea todo a múltiplos de 8.
Paul Wagland
6

La memoria total utilizada / libre de un programa se puede obtener en el programa a través de

java.lang.Runtime.getRuntime();

El tiempo de ejecución tiene varios métodos que se relacionan con la memoria. El siguiente ejemplo de codificación demuestra su uso.

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));
     }
 }
nazar_art
fuente
6

Parece que cada objeto tiene una sobrecarga de 16 bytes en sistemas de 32 bits (y 24 bytes en sistemas de 64 bits).

http://algs4.cs.princeton.edu/14analysis/ es una buena fuente de información. Un ejemplo entre muchos buenos es el siguiente.

ingrese la descripción de la imagen aquí

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf también es muy informativo, por ejemplo:

ingrese la descripción de la imagen aquí

Arun
fuente
"Parece que cada objeto tiene una sobrecarga de 16 bytes en sistemas de 32 bits (y 24 bytes en sistemas de 64 bits)". No es correcto, al menos para los JDK actuales. Echa un vistazo a mi respuesta para el ejemplo de Integer. La sobrecarga del objeto tiene al menos 12 bytes para el encabezado del sistema de 64 bits y el JDK moderno. Puede ser más debido al relleno, depende del diseño real de los campos en el objeto.
Dmitry Spikhalskiy
El segundo enlace al tutorial de Java eficiente en memoria parece estar muerto: me aparece "Prohibido".
tsleyson
6

¿El espacio de memoria consumido por un objeto con 100 atributos es el mismo que el de 100 objetos, con un atributo cada uno?

No.

¿Cuánta memoria se asigna a un objeto?

  • La sobrecarga es de 8 bytes en 32 bits, 12 bytes en 64 bits; y luego se redondea a un múltiplo de 4 bytes (32 bits) u 8 bytes (64 bits).

¿Cuánto espacio adicional se usa al agregar un atributo?

  • Los atributos varían de 1 byte (byte) a 8 bytes (largo / doble), pero las referencias son de 4 bytes o de 8 bytes, no dependiendo de si es de 32 bits o de 64 bits, sino de si -Xmx es <32 Gb o> = 32 Gb: típico 64 Las JVM de -bit tienen una optimización llamada "-UseCompressedOops" que comprime las referencias a 4 bytes si el montón está por debajo de 32Gb.
Jayen
fuente
1
un carácter es de 16 bits, no de 8 bits.
comonad
estás en lo correcto. alguien parece haber editado mi respuesta original
Jayen
5

No, registrar un objeto también requiere un poco de memoria. 100 objetos con 1 atributo ocuparán más memoria.

Mendelt
fuente
4

La pregunta será muy amplia.

Depende de la variable de clase o puede llamar como estados el uso de memoria en java.

También tiene algunos requisitos de memoria adicionales para encabezados y referencias.

La memoria de almacenamiento dinámico utilizada por un objeto Java incluye

  • memoria para campos primitivos, de acuerdo con su tamaño (ver a continuación los tamaños de los tipos primitivos);

  • memoria para campos de referencia (4 bytes cada uno);

  • un encabezado de objeto, que consta de unos pocos bytes de información de "limpieza";

Los objetos en Java también requieren cierta información de "limpieza", como el registro de la clase de un objeto, la identificación y los indicadores de estado, como si el objeto está actualmente accesible, actualmente bloqueado por sincronización, etc.

El tamaño del encabezado del objeto Java varía en 32 y 64 bits jvm.

Aunque estos son los principales consumidores de memoria, jvm también requiere campos adicionales a veces, como la alineación del código, etc.

Tamaños de tipos primitivos

booleano y byte - 1

char y short - 2

int & float - 4

largo y doble - 8

Nikhil Agrawal
fuente
Los lectores también pueden encontrar este documento muy esclarecedor: cs.virginia.edu/kim/publicity/pldi09tutorials/…
quellish
2

En caso de que sea útil para alguien, puede descargar desde mi sitio web un pequeño agente Java para consultar el uso de memoria de un objeto . También le permitirá consultar el uso de memoria "profunda".

Neil Coffey
fuente
Esto funcionó muy bien para obtener una estimación aproximada de la cantidad de memoria que (String, Integer)utiliza un caché de guayaba, por elemento. ¡Gracias!
Steve K
1

no, 100 objetos pequeños necesitan más información (memoria) que uno grande.

Burkhard
fuente
0

Las reglas sobre cuánta memoria se consume dependen de la implementación de JVM y la arquitectura de la CPU (32 bits frente a 64 bits, por ejemplo).

Para conocer las reglas detalladas de SUN JVM, consulte mi antiguo blog

Saludos, Markus

kohlerm
fuente
Estoy bastante seguro de que Sun Java 1.6 64 bits necesita 12 bytes para un objeto simple + 4 relleno = 16; un objeto + un campo entero = 12 + 4 = 16
AlexWien
¿Cerraste tu blog?
Johan Boulé
En realidad no, no estoy seguro de que los blogs de SAP se hayan movido de alguna manera. La mayor parte se puede encontrar aquí kohlerm.blogspot.com
kohlerm