Ejecutar el siguiente código en Windows 10 / OpenJDK 11.0.4_x64 produce como salida used: 197
y expected usage: 200
. Esto significa que las matrices de 200 bytes de un millón de elementos ocupan aprox. 200 MB de RAM. Todo bien
Cuando cambio la asignación de la matriz de bytes en el código de new byte[1000000]
a new byte[1048576]
(es decir, a 1024 * 1024 elementos), se produce como salida used: 417
y expected usage: 200
. ¿Que demonios?
import java.io.IOException;
import java.util.ArrayList;
public class Mem {
private static Runtime rt = Runtime.getRuntime();
private static long free() { return rt.maxMemory() - rt.totalMemory() + rt.freeMemory(); }
public static void main(String[] args) throws InterruptedException, IOException {
int blocks = 200;
long initiallyFree = free();
System.out.println("initially free: " + initiallyFree / 1000000);
ArrayList<byte[]> data = new ArrayList<>();
for (int n = 0; n < blocks; n++) { data.add(new byte[1000000]); }
System.gc();
Thread.sleep(2000);
long remainingFree = free();
System.out.println("remaining free: " + remainingFree / 1000000);
System.out.println("used: " + (initiallyFree - remainingFree) / 1000000);
System.out.println("expected usage: " + blocks);
System.in.read();
}
}
Mirando un poco más profundo con visualvm, veo en el primer caso todo como se esperaba:
En el segundo caso, además de las matrices de bytes, veo el mismo número de matrices int que ocupan la misma cantidad de RAM que las matrices de bytes:
Estas matrices int, por cierto, no muestran que estén referenciadas, pero no puedo recolectarlas de forma basura ... (Las matrices de bytes muestran muy bien dónde están referenciadas).
¿Alguna idea de lo que está pasando aquí?
fuente
int[]
para emular unabyte[]
localidad espacial grande y mejor?Respuestas:
Lo que esto describe es el comportamiento listo para usar del recolector de basura G1, que por defecto suele ser "regiones" de 1 MB y se convirtió en un valor predeterminado de JVM en Java 9. La ejecución con otros GC habilitados proporciona números variables.
Corrí
java -Xmx300M -XX:+PrintGCDetails
y muestra que el montón está agotado por las regiones gigantescas:Queremos que nuestro 1MiB
byte[]
sea "menos de la mitad del tamaño de la región G1", por lo que agregar-XX:G1HeapRegionSize=4M
ofrece una aplicación funcional:Descripción detallada de G1: https://www.oracle.com/technical-resources/articles/java/g1gc.html
Detalle aplastante de G1: https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552
fuente
long[1024*1024]
Probé el código con el que da un uso esperado de 1600M con G1, que varía según-XX:G1HeapRegionSize
[1M usado: 1887, 2M usado: 2097, 4M usado: 3358, 8M usado: 3358, 16M usado: 3363, 32M usado: 1682]. Con-XX:+UseConcMarkSweepGC
usado: 1687. Con-XX:+UseZGC
usado: 2105. Con-XX:+UseSerialGC
usado: 1698used: 417 expected usage: 400
pero si lo elimino-2
, cambiará aused: 470
unos 50 MB, y 50 * 2 largos definitivamente son mucho menos de 50 MB[0.297s][info ][gc,heap ] GC(18) Humongous regions: 450->450
1024 * 1024-2 ->[0.292s][info ][gc,heap ] GC(20) Humongous regions: 400->400
Prueba que esos dos últimos largos obligan a G1 a asignar otra región de 1 MB solo para almacenar 16 bytes.