web-dev-qa-db-fra.com

Java tableau d'octets de 1 Mo ou plus occupe deux fois le RAM

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:

byte arrays take up 200mb

Dans le deuxième cas, en plus des tableaux d'octets, je vois le même nombre de tableaux int prenant la même quantité de RAM que les tableaux d'octets:

int arrays take up additional 200mb

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 ordures ... (Les tableaux d'octets montrent très bien où ils sont référencés.)

Des idées ce qui se passe ici?

14
Georg

Ce que cela décrit, c'est le comportement prêt à l'emploi du G1 garbage collector qui par défaut est généralement de 1 Mo "régions" et est devenu un défaut JVM dans Java 9 L'exécution avec d'autres GC activés donne des nombres variables.

tout objet dont la taille est supérieure à la moitié d'une région est considéré comme "gigantesque" ... Pour les objets qui sont légèrement plus grands qu'un multiple de la taille de la région du tas, cet espace inutilisé peut entraîner la fragmentation du tas.

L'Iran Java -Xmx300M -XX:+PrintGCDetails et cela montre que le tas est épuisé par des régions énormes:

[0.202s][info   ][gc,heap        ] GC(51) Old regions: 1->1
[0.202s][info   ][gc,heap        ] GC(51) Archive regions: 2->2
[0.202s][info   ][gc,heap        ] GC(51) Humongous regions: 296->296
[0.202s][info   ][gc             ] GC(51) Pause Full (G1 Humongous Allocation) 297M->297M(300M) 1.935ms
[0.202s][info   ][gc,cpu         ] GC(51) User=0.01s Sys=0.00s Real=0.00s
...
Exception in thread "main" Java.lang.OutOfMemoryError: Java heap space

Nous voulons que notre 1MiB byte[] à "moins de la moitié de la taille de la région G1", donc ajoutez -XX:G1HeapRegionSize=4M donne une application fonctionnelle:

[0.161s][info   ][gc,heap        ] GC(19) Humongous regions: 0->0
[0.161s][info   ][gc,metaspace   ] GC(19) Metaspace: 320K->320K(1056768K)
[0.161s][info   ][gc             ] GC(19) Pause Full (System.gc()) 274M->204M(300M) 9.702ms
remaining free: 100
used: 209
expected usage: 200

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

9
drekbour