Introduccion
¿El objeto Java contiene:
- campos declarados en superclase?
- campos privados declarados en una superclase?
- métodos?
- elementos de la matriz?
- longitud de la matriz?
- otro objeto (en sí mismo)?
- código hash?
- tipo (propio)?
- nombre (propio)?
Las respuestas a estas (y otras) preguntas se pueden obtener utilizando
la biblioteca de clases org.openjdk.jol que, en particular, nos permite comprender que el objeto es un área de memoria:
- que contiene:
- encabezado (hasta 16 bytes), y en él:
- código hash
- referencia de tipo
- longitud de matriz (para matriz)
- todos los campos (incluidos los privados) declarados en todas las superclases
- o elementos de matriz (para una matriz)
- sin contener:
- variables estáticas
- métodos
- otros objetos en ti
- nombre propio (es decir, el objeto no tiene nombre)
Preparación
Aquí están los resultados de evaluar la memoria de objetos de varios tipos mediante el método de la descripción del
paquete java.lang.instrument (ver también
aquí ). Estos resultados nos permiten responder a la mayoría de las preguntas planteadas anteriormente.
Deben completarse los siguientes pasos:
- Cree una clase de agente que contenga el método principal:
public static void premain(String, Instrumentation) {...}
- Cree un archivo que contenga la clase de agente y el archivo de manifiesto con el contenido:
Premain-class: --
- Cree una clase ejecutable para evaluar la memoria.
- Especifique el archivo con el parámetro "-javaagent" al iniciar la máquina virtual:
java -javaagent:- --
Comencemos con un caso de prueba. Para simplificar, usamos un paquete sin nombre.
Paso 1. Crear una clase de agente de prueba
import java.lang.instrument.Instrumentation; public class A { public static void premain(String notUsedHere, Instrumentation i) { System.out.println("premain"); } }
Compilamos:
javac A.java
Paso 2. Cree un archivo de manifiesto m.txt que contenga:
Premain-class: A
ATENCIÓN: la segunda línea del archivo debe estar VACÍA, SIN CONTENER ESPACIOS.Crea el archivo A.jar:
jar cmf m.txt A.jar A.class
Paso 3. Crea una clase ejecutable de prueba
public class M { public static void main(String[] notUsedHere) {
Compilamos:
javac M.java
Paso 4. Realizar
java -javaagent:A.jar M
Resultado:
premain main
indica que primero se llamó al método principal de la clase de agente y luego al método principal de la clase ejecutable.
Ahora cree la clase de agente requerida:
import java.lang.instrument.Instrumentation; public class A {
y clase ejecutable:
class M { public static void main(String[] notUsedHere) { mem("Object", new Object()); } private static void mem(Object o, Object ref) { System.out.println(o + ": " + objectBytesEstimate(ref)); } private static long objectBytesEstimate(Object ref) { if (A.instrumentation() == null) { throw new RuntimeException("Not initialized instrumentation."); } return A.instrumentation().getObjectSize(ref); } }
Método
long getObjectSize(Object --)
devuelve una ESTIMACIÓN del tamaño (número de bytes) de memoria ocupada por el objeto en el enlace especificado. Debe tenerse en cuenta que la estimación resultante puede ser diferente para otra máquina virtual. Los valores para
jdk-13 se enumerarán
aquí .
Realizamos:
javac *.java jar cmf m.txt A.jar A.class java -javaagent:A.jar M
y obtenemos el resultado:
Object: 16
mostrando que un objeto VACÍO de tipo Objeto ocupa aquí (POR EVALUACIÓN) 16 bytes. De estos, 12 bytes ocupan el encabezado, y 4 bytes al final sirven para alinear la longitud del objeto al límite de 8 bytes.
Resultados
Otros ejemplos contendrán solo el código colocado en el método principal de la clase M. Deben ejecutarse para cada ejemplo con los comandos:
javac M.java java -javaagent:A.jar M
Volver a crear A.jar no es necesario.
Por ejemplo, para obtener una estimación del tamaño de la memoria de un objeto de un tipo arbitrario sin campos, colocamos el código en el método principal:
class C {}; mem("Empty", new C());
El resultado indicado en el comentario muestra que un objeto sin campos ocupa tantos bytes como un objeto de tipo Objeto.
Además, el resultado del programa:
{class C {int a; } mem(1, new C());}
indica que cada campo int toma 4 bytes. Observo que aquí cada línea es un bloque separado, que le permite usar el mismo nombre para diferentes clases.
Cada campo largo es de 8 bytes:
{class C {long a; } mem(1, new C());}
Cada campo booleano toma 1 byte (para esta VM):
{class C {boolean a; } mem(1, new C());}
Cada campo de referencia toma 4 bytes (para esta VM):
{class C {Boolean a; } mem(1, new C());}
Un campo de tipo cadena también toma 4 bytes, como cada referencia:
{class C {String a; } mem(" null", new C());}
Un campo de referencia de matriz también toma 4 bytes, como cada referencia:
{class C {int[] a; } mem("null", new C());}
El objeto de subtipo contiene cada campo declarado en la superclase, independientemente del modificador de acceso:
{class S { } class C extends S {long a;} mem("0+1", new C());}
El objeto de subtipo contiene un campo declarado en la superclase con el mismo nombre que en la subclase (el llamado oculto - oculto):
{class S { } class C extends S {long a,b;} mem("0+2", new C());}
Un objeto de subtipo contiene cada campo declarado en cada una de sus superclases:
class U {private long a; } class S extends U {private long a; } class C extends S { long a; } mem("1+1+1", new C());
A su vez a las matrices. Como sabes, una matriz es un tipo especial de objeto cuyos elementos están en el objeto mismo, por lo que el tamaño de la memoria ocupada por la matriz crece con el número de elementos:
{long[] a = new long[ 0]; mem(" 0", a);}
Y para la variedad de enlaces:
{Long[] a = new Long[ 0]; mem(" 0", a);}
Ahora, por curiosidad, comparamos los tamaños de varios objetos de diferentes tipos:
mem(" Object", new Object());
Lo mismo para diferentes jdk en un procesador de 64 bits:
jdk1.6.0_45 jdk1.7.0_80 jdk1.8.0_191 jdk-9 jdk-12 jdk-13
----------- ----------- ------------ ------ ------ ---- -
Objeto: 16 16 16 16 16 16
Cadena: 32 24 24 24 24 24
Excepción: 32 32 32 40 40 40
clase int .: 104 88 104 112 104 112
int []. clase: 584 544 480 112 104 112
Clase de objeto: 600560496112104121
Clase del sistema: 624560496144152160
Cadena.clase: 696 640 624 136 128 136
La estimación del tamaño de la cadena es de 24 bytes, aunque la clase String contiene muchas variables estáticas, métodos estáticos y no estáticos. Indudablemente, esto indica la ausencia de variables estáticas y código de método en el objeto. Lo mismo es cierto para un objeto de cualquier tipo.
En conclusión, un recordatorio: todos los datos sobre el tamaño del objeto se estiman y pueden variar hasta cierto punto de una ejecución a otra y, por supuesto, para diferentes máquinas virtuales.