Ejecutar el siguiente código en Windows 10 / OpenJDK 11.0.4_x64 produce como salida used: 197y 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: 417y 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:+PrintGCDetailsy 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=4Mofrece 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:+UseConcMarkSweepGCusado: 1687. Con-XX:+UseZGCusado: 2105. Con-XX:+UseSerialGCusado: 1698used: 417 expected usage: 400pero si lo elimino-2, cambiará aused: 470unos 50 MB, y 50 * 2 largos definitivamente son mucho menos de 50 MB[0.297s][info ][gc,heap ] GC(18) Humongous regions: 450->4501024 * 1024-2 ->[0.292s][info ][gc,heap ] GC(20) Humongous regions: 400->400Prueba que esos dos últimos largos obligan a G1 a asignar otra región de 1 MB solo para almacenar 16 bytes.