web-dev-qa-db-fra.com

Comment puis-je obtenir la mémoire que mon programme Java utilise via l'API d'exécution Java?

Il existe des questions similaires, mais elles semblent éviter de répondre à cette question spécifique. Comment obtenir la mémoire utilisée par mon programme Java via l'API Runtime de Java?

La réponse ici indique que je peux faire quelque chose comme ceci:

System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);

Mais cela retourne toujours le même nombre, quel que soit le programme que je lance. Par exemple, ci-dessous, j'ai un programme dans lequel, peu importe le nombre de chiffres que j'ai mis dans la carte, l'utilisation de la mémoire reste la même.

package memoryTest;

import Java.util.HashMap;
import Java.util.Map;

public class MemoryTest {

    static Map<Integer, NewObject> map = new HashMap<Integer, NewObject>();

    public static void main(String[] args){

        System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);
        fillMemory(25);

        System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);
    }

    static int j=0;
    public static void fillMemory(int i){

        for(int k=0; k< 2000; k++)
            map.put(j++, new NewObject());

    }


    public static class NewObject{
        long i = 0L;
        long j = 0L;
        long k = 0L;
    }

}

via la méthode principale de cambecc, la sortie est:

3085, Total: 128516096, Libre: 127173744, Diff: 671120

173579, Total: 128516096, Libre: 110033976, Diff: 671128

335207, Total: 128516096, Libre: 92417792, Diff: 637544

672788, Total: 224198656, Libre: 159302960, Diff: 1221520

1171480, Total: 224198656, Libre: 106939136, Diff: 1221544

1489771, Total: 368377856, Libre: 227374816, Diff: 1212984

1998743, Total: 368377856, Gratuit: 182494408, Diff: 1212984

22
Matt

Vous le faites correctement. Le moyen d'obtenir l'utilisation de la mémoire est exactement comme vous l'avez décrit:

Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()

Mais la raison pour laquelle votre programme retourne toujours la même utilisation de la mémoire est que vous ne créez pas assez d'objets pour dépasser les limitations de précision de la méthode freeMemory. Bien qu'il ait un octet resolution, rien ne garantit que precisefreeMemory doit être. Le javadoc en dit autant:

une approximation de la quantité totale de mémoire actuellement disponible pour les futurs objets alloués, mesurée en octets.

Essayez ce qui suit, qui crée deux instances millionNewObject et affiche chaque fois que le résultat de freeMemory change:

public static void main(String[] args) {
    Runtime rt = Runtime.getRuntime();
    long prevTotal = 0;
    long prevFree = rt.freeMemory();

    for (int i = 0; i < 2_000_000; i++) {
        long total = rt.totalMemory();
        long free = rt.freeMemory();
        if (total != prevTotal || free != prevFree) {
            System.out.println(
                String.format("#%s, Total: %s, Free: %s, Diff: %s",
                    i, 
                    total,
                    free,
                    prevFree - free));
            prevTotal = total;
            prevFree = free;
        }
        map.put(i, new NewObject());
    }
}

Sur ma machine, je vois la sortie comme suit

#0, Total: 513998848, Free: 508635256, Diff: 0
#21437, Total: 513998848, Free: 505953496, Diff: 2681760
#48905, Total: 513998848, Free: 503271728, Diff: 2681768
#73394, Total: 513998848, Free: 500589960, Diff: 2681768
#103841, Total: 513998848, Free: 497908192, Diff: 2681768
...

Remarquez comment la mémoire libre signalée n'a pas changé jusqu'à ce que le 21 437e objet ait été instancié? Les chiffres suggèrent que freeMemory pour la machine virtuelle que j'utilise (Java7 Win 64 bits) a une précision d'un peu plus de 2,5 Mo (bien que si vous exécutez l'expérience, vous verrez que ce nombre varie).

-- Modifier --

Ce code est le même que ci-dessus, mais affiche plus de détails sur l'utilisation de la mémoire. J'espère que la façon dont la JVM utilise la mémoire se comporte un peu plus clairement. Nous allouons continuellement de nouveaux objets dans une boucle. Lors de chaque itération, si totalMemory ou freeMemory est identique à la dernière itération, nous n'imprimons rien. Mais si l'un ou l'autre a changé, nous rapportons l'utilisation actuelle de la mémoire. Les valeurs représentent la différence entre l'utilisation actuelle et le rapport de mémoire précédent.

public static void main(String[] args) {
    Runtime rt = Runtime.getRuntime();
    long prevTotal = 0;
    long prevFree = rt.freeMemory();

    for (int i = 0; i < 2_000_000; i++) {
        long total = rt.totalMemory();
        long free = rt.freeMemory();
        if (total != prevTotal || free != prevFree) {
            long used = total - free;
            long prevUsed = (prevTotal - prevFree);
            System.out.println(
                "#" + i +
                ", Total: " + total +
                ", Used: " + used +
                ", ∆Used: " + (used - prevUsed) +
                ", Free: " + free +
                ", ∆Free: " + (free - prevFree));
            prevTotal = total;
            prevFree = free;
        }
        map.put(i, new NewObject());
    }
}

Sur mon cahier, je vois la sortie suivante. Notez que vos résultats seront différents selon le système d'exploitation, le matériel, la mise en œuvre JVM, etc.:

#0, Total: 83427328, Used: 1741048, ∆Used: 83427328, Free: 81686280, ∆Free: 0
#3228, Total: 83427328, Used: 1741080, ∆Used: 32, Free: 81686248, ∆Free: -32
#3229, Total: 83427328, Used: 2176280, ∆Used: 435200, Free: 81251048, ∆Free: -435200
#7777, Total: 83427328, Used: 2176312, ∆Used: 32, Free: 81251016, ∆Free: -32
#7778, Total: 83427328, Used: 2611536, ∆Used: 435224, Free: 80815792, ∆Free: -435224
...
#415056, Total: 83427328, Used: 41517072, ∆Used: 407920, Free: 41910256, ∆Free: -407920
#419680, Total: 145358848, Used: 39477560, ∆Used: -2039512, Free: 105881288, ∆Free: 63971032
#419681, Total: 145358848, Used: 40283832, ∆Used: 806272, Free: 105075016, ∆Free: -806272
...

Il y a quelques observations à partir de ces données:

  1. La mémoire utilisée tend à augmenter, comme prévu. La mémoire utilisée inclut les objets en direct et les déchets.
  2. Mais la mémoire utilisée diminue pendant un CPG, car des ordures ont été jetées. Par exemple, cela s'est produit à # 419680.
  3. La quantité de mémoire disponible est réduite en morceaux, pas octet par octet. Les morceaux varient en taille. Parfois, les morceaux sont vraiment minuscules, comme 32 octets, mais ils sont généralement plus grands, comme 400K ou 800K. Il semble donc que la taille du bloc varie beaucoup. Mais comparée à la taille totale du tas, la variation semble minuscule. Par exemple, sous # 419681, la taille du bloc ne représente que 0,6% de la taille totale du segment de mémoire.
  4. La mémoire disponible a tendance à diminuer, comme prévu, jusqu'à ce qu'un GC entre en action et nettoie les déchets. Lorsque cela se produit, la mémoire disponible augmente considérablement, en fonction de la quantité de déchets mis au rebut.
  5. Ce test génère beaucoup de déchets. Au fur et à mesure que la hashmap grossit, elle réorganise son contenu, générant ainsi beaucoup de déchets.
44
cambecc

J'ai les méthodes suivantes

public static long getMaxMemory() {
    return Runtime.getRuntime().maxMemory();
}

public static long getUsedMemory() {
    return getMaxMemory() - getFreeMemory();
}

public static long getTotalMemory() {
    return Runtime.getRuntime().totalMemory();
}

public static long getFreeMemory() {
    return Runtime.getRuntime().freeMemory();
}

qui retournent la mémoire (utilisée) en octets.

Si vous voulez recalculer en MiB j'ai:

private static final long MEGABYTE_FACTOR = 1024L * 1024L;
private static final DecimalFormat ROUNDED_DOUBLE_DECIMALFORMAT;
private static final String MIB = "MiB";

static {
    DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(Locale.ENGLISH);
    otherSymbols.setDecimalSeparator('.');
    otherSymbols.setGroupingSeparator(',');
    ROUNDED_DOUBLE_DECIMALFORMAT = new DecimalFormat("####0.00", otherSymbols);
    ROUNDED_DOUBLE_DECIMALFORMAT.setGroupingUsed(false);
}


    public static String getTotalMemoryInMiB() {
        double totalMiB = bytesToMiB(getTotalMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(totalMiB), MIB);
    }

    public static String getFreeMemoryInMiB() {
        double freeMiB = bytesToMiB(getFreeMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(freeMiB), MIB);
    }

    public static String getUsedMemoryInMiB() {
        double usedMiB = bytesToMiB(getUsedMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(usedMiB), MIB);
    }

    public static String getMaxMemoryInMiB() {
        double maxMiB = bytesToMiB(getMaxMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(maxMiB), MIB);
    }

    public static double getPercentageUsed() {
        return ((double) getUsedMemory() / getMaxMemory()) * 100;
    }

    public static String getPercentageUsedFormatted() {
        double usedPercentage = getPercentageUsed();
        return ROUNDED_DOUBLE_DECIMALFORMAT.format(usedPercentage) + "%";
    }
1
Code.IT