A execução do código abaixo no Windows 10 / OpenJDK 11.0.4_x64 produz como saída used: 197
e expected usage: 200
. Isso significa que matrizes de 200 bytes de um milhão de elementos ocupam aprox. 200MB de RAM. Tudo bem.
Quando altero a alocação da matriz de bytes no código de new byte[1000000]
para new byte[1048576]
(isto é, para 1024 * 1024 elementos), ela produz como saída used: 417
e expected usage: 200
. Que diabos?
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();
}
}
Olhando um pouco mais fundo com o visualvm, vejo no primeiro caso tudo como esperado:
No segundo caso, além das matrizes de bytes, vejo o mesmo número de matrizes int ocupando a mesma quantidade de RAM que as matrizes de bytes:
Essas matrizes int, a propósito, não mostram que elas são referenciadas, mas não posso coletá-las com lixo ... (As matrizes de bytes mostram muito bem onde são referenciadas.)
Alguma idéia do que está acontecendo aqui?
fonte
int[]
para emular uma grandebyte[]
para melhor localidade espacial?Respostas:
O que isso descreve é o comportamento pronto para o uso do coletor de lixo G1, que normalmente padroniza 1MB de "regiões" e se tornou um padrão da JVM no Java 9. A execução com outros GCs ativados fornece números variados.
Eu corri
java -Xmx300M -XX:+PrintGCDetails
e mostra que a pilha está esgotada por regiões enormes:Queremos que nosso 1MiB
byte[]
tenha "menos da metade do tamanho da região G1", portanto, a adição-XX:G1HeapRegionSize=4M
fornece um aplicativo funcional:Visão geral detalhada do G1: https://www.oracle.com/technical-resources/articles/java/g1gc.html
Detalhes de esmagamento do G1: https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552
fonte
long[1024*1024]
qual o uso esperado é de 1600M com G1, variando em-XX:G1HeapRegionSize
[1M usado: 1887, 2M usado: 2097, 4M usado: 3358, 8M usado: 3358, 8M usado: 3358, 16M usado: 3363, 32M usado: 1682]. Com-XX:+UseConcMarkSweepGC
usado: 1687. Com-XX:+UseZGC
usado: 2105. Com-XX:+UseSerialGC
usado: 1698used: 417 expected usage: 400
mas se eu remover-2
, mudará paraused: 470
cerca de 50 MB e 50 * 2[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
Prova que os últimos dois longos forçam o G1 a alocar outra região de 1 MB apenas para armazenar 16 bytes.