L'exécution du code ci-dessous sur Windows 10 / OpenJDK 11.0.4_x64 produit en sortie used: 197
et expected usage: 200
. Cela signifie que des tableaux de 200 octets d'un million d'éléments occupent env. 200 Mo de RAM. Tout va bien.
Lorsque je modifie l'allocation du tableau d'octets dans le code de new byte[1000000]
à new byte[1048576]
(c'est-à-dire à 1024 * 1024 éléments), il produit en sortie used: 417
et expected usage: 200
. Que diable?
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();
}
}
En regardant un peu plus en profondeur avec visualvm, je vois dans le premier cas tout comme prévu:
Dans le deuxième cas, en plus des tableaux d'octets, je vois le même nombre de tableaux int qui occupent la même quantité de RAM que les tableaux d'octets:
Soit dit en passant, ces tableaux int ne montrent pas qu'ils sont référencés, mais je ne peux pas les récupérer ... (Les tableaux d'octets montrent très bien où ils sont référencés.)
Des idées ce qui se passe ici?
la source
int[]
pour émuler un grandbyte[]
pour une meilleure localité spatiale?Réponses:
Ce que cela décrit, c'est le comportement prêt à l' emploi du garbage collector G1 qui par défaut est généralement de 1 Mo "régions" et est devenu une valeur par défaut JVM dans Java 9. L'exécution avec d'autres GC activés donne des nombres variables.
J'ai couru
java -Xmx300M -XX:+PrintGCDetails
et cela montre que le tas est épuisé par des régions énormes:Nous voulons que notre 1 Mo
byte[]
soit "moins de la moitié de la taille de la région G1", donc l'ajout-XX:G1HeapRegionSize=4M
donne une application fonctionnelle:Présentation détaillée de G1: https://www.oracle.com/technical-resources/articles/java/g1gc.html
Détail écrasant de G1: https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552
la source
long[1024*1024]
qui donne une utilisation prévue de 1600M avec G1, variant de-XX:G1HeapRegionSize
[1M utilisé: 1887, 2M utilisé: 2097, 4M utilisé: 3358, 8M utilisé: 3358, 16M utilisé: 3363, 32M utilisé: 1682]. Avec-XX:+UseConcMarkSweepGC
utilisé: 1687. Avec-XX:+UseZGC
utilisé: 2105. Avec-XX:+UseSerialGC
utilisé: 1698used: 417 expected usage: 400
mais si je le supprime,-2
il passera àused: 470
environ 50 Mo, et 50 * 2 longs est certainement beaucoup moins que 50 Mo[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
Cela prouve que ces deux derniers longs forcent G1 à allouer une autre région de 1 Mo juste pour stocker 16 octets.