java - son - ¿Dalvik está aún más hambriento de memoria que HotSpot en términos de tamaños de objetos?
stack memoria dinamica (2)
(Sí, esta es una vieja pregunta, pero los resultados fueron un poco interesantes, así que me enfoqué un poco).
El método Object.clone()
necesita hacer una copia bit a bit completa de un objeto. Para hacerlo, necesita saber qué tan grande es un objeto. Si observa dvmCloneObject()
, verá que utiliza un método para matrices y un método diferente para objetos.
Para las matrices, llama a dvmArrayObjectSize()
, que multiplica la longitud de la matriz por el ancho del elemento (1, 2, 4 u 8) y luego agrega el desplazamiento de los datos de la matriz desde el inicio del objeto. Cada objeto tiene un encabezado de 8 bytes; las matrices tienen un ancho de 4 bytes e incluyen 4 bytes adicionales de relleno para garantizar que los valores de 64 bits estén alineados correctamente. Entonces, para un conjunto de 5 elementos de short
, serían 16 + 5 * 2.
Para objetos ordinarios, simplemente usa el campo objectSize
en el objeto de clase. Esto se establece mediante una función bastante complicada llamada computeFieldOffsets()
. Esa función asegura que todas las referencias de objetos sean lo primero (para que el GC pueda omitir menos al escanear), y luego sigue con todos los campos de 64 bits. Para asegurarse de que los campos de 64 bits estén alineados correctamente, puede mover uno de los campos primitivos de 32 bits para completarlo. (Si no hay un campo apropiado de 32 bits, obtendrá 4 bytes de relleno).
Debo añadir: todos los campos son de 32 bits, excepto el long
y el double
, que son de 64 bits. Las referencias de objeto son de 32 bits.
Por lo tanto, es difícil decir exactamente qué tan grande será un objeto que no sea de matriz, pero en general se toma el encabezado de objeto de 8 bytes, se suman los anchos de los campos adicionales y se redondea al siguiente múltiplo de 8 bytes: el último porque todos los objetos deben estar alineados con 64 bits.
Entonces esa es la teoría. Para verlo en la práctica, agregué esto a dvmCloneObject()
:
ALOGD("class=%s size=%d", clazz->descriptor, clazz->objectSize);
y vio salida de logcat como:
D dalvikvm: class=Ljava/util/Locale; size=24
D dalvikvm: class=Ljava/util/Date; size=16
La configuración regional tiene 4 campos de referencia, la fecha tiene un campo long
, por lo que estos valores coinciden con las expectativas.
Idealmente, eso es exactamente cuánto espacio se requeriría. Sin embargo, el objeto se asigna con mspace_calloc()
, que agrega otros 4 o (a veces) 8 bytes de sobrecarga. Por lo tanto, el espacio real requerido para los valores anteriores sería 32 y 24, que coincide con los resultados experimentales.
Me he estado preguntando cuánta memoria ocupa un Objeto en Android. Existen numerosos recursos (como this ) relacionados con HotSpot JVM que indican que un objeto vacío toma 8 bytes y un conjunto vacío 12 bytes y que todos los objetos están alineados con un límite de 8 bytes. Por lo tanto, un objeto sin campos adicionales debe tomar 8 bytes, el objeto más pequeño con al menos un campo adicional - 16 bytes, un conjunto vacío - 16 bytes, ¿verdad?
No he encontrado información específica sobre Dalvik sobre este asunto y decidí averiguarlo mediante pruebas. Ejecutar la prueba tuvo resultados sorprendentes .
Pocas palabras sobre el método de cálculo. La implementación de Android de Object.hashCode () simplemente devuelve el puntero al objeto enviado a int. (Parecía obvio y general, pero [otra sorpresa] resultó, NO en HotSpot JVM, por ejemplo, ejecutar MemTest con HotSpot y ver). Por lo tanto, he utilizado la simplicidad de hashCode () en Dalvik para calcular el tamaño del objeto en Android asignando dos instancias de la clase probada en una fila y la cantidad del espacio asignado debe ser igual a la diferencia de su hashCode () valores (suponiendo que tiene poco sentido para Dalvik asignarlos a direcciones completamente aleatorias). Solo para asegurarme de haber asignado siempre 4 objetos en una fila por clase de prueba, lo que entregó siempre la misma diferencia de hashCode (). Por lo tanto, creo que hay pocas dudas sobre la corrección del método.
Aquí está el código fuente de la prueba:
public class MemTest {
public static void run() {
Object o1 = new Object();
Object o2 = new Object();
Object o3 = new Object();
Object o4 = new Object();
EmptyObject eo1 = new EmptyObject();
EmptyObject eo2 = new EmptyObject();
EmptyObject eo3 = new EmptyObject();
EmptyObject eo4 = new EmptyObject();
ObjectWithBoolean ob1 = new ObjectWithBoolean();
ObjectWithBoolean ob2 = new ObjectWithBoolean();
ObjectWithBoolean ob3 = new ObjectWithBoolean();
ObjectWithBoolean ob4 = new ObjectWithBoolean();
ObjectWithBooleanAndInt obi1 = new ObjectWithBooleanAndInt();
ObjectWithBooleanAndInt obi2 = new ObjectWithBooleanAndInt();
ObjectWithBooleanAndInt obi3 = new ObjectWithBooleanAndInt();
ObjectWithBooleanAndInt obi4 = new ObjectWithBooleanAndInt();
ObjectWithLong ol1 = new ObjectWithLong();
ObjectWithLong ol2 = new ObjectWithLong();
ObjectWithLong ol3 = new ObjectWithLong();
ObjectWithLong ol4 = new ObjectWithLong();
ObjectWith4Ints o4i1 = new ObjectWith4Ints();
ObjectWith4Ints o4i2 = new ObjectWith4Ints();
ObjectWith4Ints o4i3 = new ObjectWith4Ints();
ObjectWith4Ints o4i4 = new ObjectWith4Ints();
ObjectWith4IntsAndByte o4ib1 = new ObjectWith4IntsAndByte();
ObjectWith4IntsAndByte o4ib2 = new ObjectWith4IntsAndByte();
ObjectWith4IntsAndByte o4ib3 = new ObjectWith4IntsAndByte();
ObjectWith4IntsAndByte o4ib4 = new ObjectWith4IntsAndByte();
ObjectWith5Ints o5i1 = new ObjectWith5Ints();
ObjectWith5Ints o5i2 = new ObjectWith5Ints();
ObjectWith5Ints o5i3 = new ObjectWith5Ints();
ObjectWith5Ints o5i4 = new ObjectWith5Ints();
ObjectWithArrayRef oar1 = new ObjectWithArrayRef();
ObjectWithArrayRef oar2 = new ObjectWithArrayRef();
ObjectWithArrayRef oar3 = new ObjectWithArrayRef();
ObjectWithArrayRef oar4 = new ObjectWithArrayRef();
byte[] a0b1 = new byte[0];
byte[] a0b2 = new byte[0];
byte[] a0b3 = new byte[0];
byte[] a0b4 = new byte[0];
byte[] a1b1 = new byte[1];
byte[] a1b2 = new byte[1];
byte[] a1b3 = new byte[1];
byte[] a1b4 = new byte[1];
byte[] a5b1 = new byte[5];
byte[] a5b2 = new byte[5];
byte[] a5b3 = new byte[5];
byte[] a5b4 = new byte[5];
byte[] a9b1 = new byte[9];
byte[] a9b2 = new byte[9];
byte[] a9b3 = new byte[9];
byte[] a9b4 = new byte[9];
byte[] a12b1 = new byte[12];
byte[] a12b2 = new byte[12];
byte[] a12b3 = new byte[12];
byte[] a12b4 = new byte[12];
byte[] a13b1 = new byte[13];
byte[] a13b2 = new byte[13];
byte[] a13b3 = new byte[13];
byte[] a13b4 = new byte[13];
print("java.lang.Object", o1, o2, o3, o4);
print("Empty object", eo1, eo2, eo3, eo4);
print("Object with boolean", ob1, ob2, ob3, ob4);
print("Object with boolean and int", obi1, obi2, obi3, obi4);
print("Object with long", ol1, ol2, ol3, ol4);
print("Object with 4 ints", o4i1, o4i2, o4i3, o4i4);
print("Object with 4 ints and byte", o4ib1, o4ib2, o4ib3, o4ib4);
print("Object with 5 ints", o5i1, o5i2, o5i3, o5i4);
print("Object with array ref", new Object[]{oar1, oar2, oar3, oar4});
print("new byte[0]", a0b1, a0b2, a0b3, a0b4);
print("new byte[1]", a1b1, a1b2, a1b3, a1b4);
print("new byte[5]", a5b1, a5b2, a5b3, a5b4);
print("new byte[9]", a9b1, a9b2, a9b3, a9b4);
print("new byte[12]", a12b1, a12b2, a12b3, a12b4);
print("new byte[13]", a13b1, a13b2, a13b3, a13b4);
}
static void print(String title, Object... objects) {
StringBuilder buf = new StringBuilder(title).append(":");
int prevHash = objects[0].hashCode();
int prevDiff = -1;
for (int i = 1; i < objects.length; i++) {
int hash = objects[i].hashCode();
int diff = Math.abs(hash - prevHash);
if (prevDiff == -1 || prevDiff != diff) {
buf.append('' '').append(diff);
}
prevDiff = diff;
prevHash = hash;
}
System.out.println(buf.toString());
}
/******** Test classes ******/
public static class EmptyObject {
}
public static class ObjectWith4Ints {
int i1;
int i2;
int i3;
int i4;
}
public static class ObjectWith4IntsAndByte {
int i1;
int i2;
int i3;
int i4;
byte b;
}
public static class ObjectWith5Ints {
int i1;
int i2;
int i3;
int i4;
int i5;
}
public static class ObjectWithArrayRef {
byte[] b;
}
public static class ObjectWithBoolean {
boolean b;
}
public static class ObjectWithBooleanAndInt {
boolean b;
int i;
}
public static class ObjectWithLong {
long l;
}
}
Y aquí están los resultados:
java.lang.Object: 16
Empty object: 16
Object with boolean: 16
Object with boolean and int: 24
Object with long: 24
Object with 4 ints: 32
Object with 4 ints and byte: 32
Object with 5 ints: 32
Object with array ref: 16
new byte[0]: 24
new byte[1]: 24
new byte[5]: 32
new byte[9]: 32
new byte[12]: 32
new byte[13]: 40
Para resumir los resultados:
La alineación de límites de 8 bytes es la misma que en HotSpot, y eso es lo único que es igual.
un mínimo de 16 bytes para un Objeto simple (vs 8 en HotSpot)
aparentemente, un objeto vacío ocupa 12 bytes (frente a 8 en HotSpot) y hay espacio para 4 bytes adicionales hasta que el tamaño del objeto ''salta'' de 16 bytes al siguiente límite de 24 bytes.
un mínimo de 24 bytes para una matriz vacía (frente a 12 en HotSpot)
de manera similar, una matriz ocupa 20 bytes (frente a 12 en HotSpot) y hay espacio para 4 bytes adicionales de datos de matriz hasta que el tamaño del objeto "salta" de 24 bytes al siguiente límite de 32 bytes.
ADICIÓN: (en respuesta a la sugerencia de Louis) Otra prueba de estrés muestra que incluso asignando un millón de instancias de objetos, la distancia entre dos cualquiera es NUNCA menos de 16 bytes. Esa es la prueba de que los agujeros potenciales de 8 bytes entre los objetos son definitivamente espacio muerto para futuras asignaciones, de lo contrario, cuando se haya asignado la mitad de la memoria para Objetos, dalvik debería haber estado también algunos de ellos en ''agujeros'', y la prueba de esfuerzo devolvería 8, no 16.
public static void run2() {
int count = 1024 * 1024;
Object[] arr = new Object[count];
for (int i = 0; i < count; i++) {
arr[i] = new Object();
}
int[] hashes = new int[count];
for (int i = 0; i < count; i++) {
hashes[i] = arr[i].hashCode();
}
Arrays.sort(hashes);
int minDist = Integer.MAX_VALUE;
for (int i = 1; i < count; i++) {
int dist = Math.abs(hashes[i] - hashes[i - 1]);
if (dist < minDist) {
minDist = dist;
}
}
System.out.println("Allocated "+ count + " Objects, minimum distance is "+ minDist);
}
¿Veo bien que el Objeto de Dalvik ocupa hasta 8 bytes más y la matriz 8-12 bytes más en comparación con HotSpot?
No tengo las respuestas para ti, pero puedo sugerirte un par de lugares que podrías buscar en la fuente para obtener más información.
Puede echar un vistazo a las estructuras DataObject y ArrayObject en dalvik / vm / oo / Object.h. Basado en esto, parece que un objeto vacío solo debería tomar 8 bytes, mientras que un conjunto vacío debería tomar 12 bytes. Esto no parece coincidir con sus resultados, aunque no estoy seguro de por qué.
También puede ver los usos del campo objectSize en la estructura ClassObject para obtener más información. Una búsqueda rápida de usos de ese campo muestra que el método dvmAllocObject en dalvik / vm / alloc / Alloc.cpp parece ser responsable de asignar la memoria para objetos nuevos.