web-dev-qa-db-fra.com

Tests unitaires Java: comment mesurer l'encombrement mémoire pour l'appel de méthode

En supposant que j'ai une classe qui effectue un traitement lourd, fonctionnant avec plusieurs collections. Ce que je veux faire, c'est m'assurer que de telles opérations ne peuvent pas conduire à un manque de mémoire ou, mieux, je veux définir un seuil de mémoire utilisable.

class MyClass()
{
   public void myMethod()
   {
      for(int i=0; i<10000000; i++)
      {
         // Allocate some memory, may be several collections
      }
   }
}

class MyClassTest
{
   @Test
   public void myMethod_makeSureMemoryFootprintIsNotBiggerThanMax()
   {
      new MyClass().myMethod(); 
      // How do I measure amount of memory it may try to allocate?
   }
}

Quelle est la bonne approche pour faire cela? Ou ce n'est pas possible/impossible?

28
Sergey Makarov

Je peux penser à plusieurs options:

  • Déterminer la quantité de mémoire requise par votre méthode via un micro-repère (c'est-à-dire jmh ). 
  • Construire des stratégies d'allocation basées sur une estimation heuristique. Il existe plusieurs solutions open source implémentant une estimation de la taille de classe, à savoir ClassSize . Une méthode beaucoup plus simple consiste à utiliser un cache qui libère des objets rarement utilisés (par exemple, le cache de Guava). Comme mentionné par @EnnoShioji, le cache de Guava a des stratégies d'éviction basées sur la mémoire.

Vous pouvez également écrire votre propre test d'évaluation qui compte la mémoire. L'idée est de

  1. Avoir un seul fil en cours d'exécution.
  2. Créez un nouveau tableau pour stocker vos objets à allouer. Ainsi, ces objets ne seront pas collectés lors de l'exécution du CPG.
  3. System.gc(), memoryBefore = runtime.totalMemory() - runtime.freeMemory()
  4. Allouer vos objets. Mettez-les dans le tableau.
  5. System.gc(), memoryAfter = runtime.totalMemory() - runtime.freeMemory()

C’est une technique que j’ai utilisée dans mon outil léger de micro-benchmark qui est capable de mesurer l’allocation de mémoire avec une précision en octets.

14
Andrey Chaschev

Vous pouvez utiliser le profileur (par exemple, JProfiler) pour afficher la mémoire utilisée par les classes. Ou, comment mentionné Areo, imprimez simplement l’utilisation de la mémoire:

    Runtime runtime = Runtime.getRuntime();
    long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory();
    System.out.println("Used Memory before" + usedMemoryBefore);
        // working code here
    long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory();
    System.out.println("Memory increased:" + (usedMemoryAfter-usedMemoryBefore));
13
pasha701

Pour mesurer l'utilisation actuelle de la mémoire, utilisez:

Runtime.getRuntime().freeMemory(), Runtime.getRuntime().totalMemory()

Voici un bon exemple: Obtenir des informations système au niveau du système d'exploitation

Mais cette mesure n’est pas précise, mais elle peut vous donner beaucoup d’informations ... Un autre problème est le nom GC qui est imprévisible.

2
Areo

Voici un exemple de Netty qui fait quelque chose de similaire: MemoryAwareThreadPoolExecutor . La classe cache de Guava a également une expulsion basée sur la taille. Vous pouvez regarder ces sources et copier ce qu'elles font. En particulier, voici comment Netty est estime la taille des objets . Essentiellement, vous estimeriez la taille des objets que vous générez dans la méthode et gardiez un compte.

Obtenir des informations globales sur la mémoire (par exemple, combien de tas est disponible/utilisé) vous aidera à décider de la quantité de mémoire à allouer à la méthode, mais non à suivre la quantité de mémoire utilisée par les appels de méthode individuels.

Cela dit, il est très rare que vous ayez légitimement besoin de cela. Dans la plupart des cas, limiter l'utilisation de la mémoire en limitant le nombre d'objets pouvant être présents à un moment donné (par exemple, en utilisant une file d'attente limitée) est suffisant et beaucoup, beaucoup plus simple à implémenter. 

2
Enno Shioji

Cette question est un peu délicate, en raison de la manière dont Java peut allouer de nombreux objets éphémères au cours du traitement, qui seront ensuite collectés lors du nettoyage de la mémoire. Dans la réponse acceptée, nous ne pouvons pas affirmer avec certitude que le ramassage des ordures a été exécuté à un moment donné. Même si nous introduisons une structure de boucle, avec plusieurs appels System.gc(), un garbage collection peut s’exécuter entre nos appels de méthode.

Une meilleure façon consiste à utiliser plutôt une variante de ce qui est suggéré dans https://cruftex.net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your-Java- Benchmarks.html , où System.gc() est déclenché mais nous attendons également que le nombre de GC rapporté augmente:

long getGcCount() {
    long sum = 0;
    for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) {
        long count = b.getCollectionCount();
        if (count != -1) { sum += count; }
    }
    return sum;
}

long getReallyUsedMemory() {
    long before = getGcCount();
    System.gc();
    while (getGcCount() == before);
    return getCurrentlyAllocatedMemory();
}

long getCurrentlyAllocatedMemory() {
    final Runtime runtime = Runtime.getRuntime();
    return (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
}

Cela ne donne toujours qu’une approximation de la mémoire réellement allouée par votre code à un moment donné, mais la valeur est généralement beaucoup plus proche de ce à quoi on pourrait s’intéresser habituellement.

0