Guide de System.gc ()

1. Vue d'ensemble

Dans ce tutoriel, nous allons étudier la méthode System.gc () située dans le package java.lang .

L'appel explicite de System.gc () est connu pour être une mauvaise pratique. Essayons de comprendre pourquoi et s'il y a des cas d'utilisation lors de l'appel de cette méthode pourrait être utile.

2. Collecte des ordures

La machine virtuelle Java décide d'effectuer un garbage collection lorsqu'il y a des indications pour le faire. Ces indications diffèrent d'une implémentation du GC à l'autre. Ils sont basés sur différentes heuristiques. Cependant, il y a quelques instants où GC sera exécuté à coup sûr:

  • La nouvelle génération (espace permanent) est pleine, ce qui déclenche un GC mineur
  • L'ancienne génération (espaces Eden + Survivor0 + Survivor1) est pleine, ce qui déclenche un GC majeur / complet

La seule chose qui est indépendante de l'implémentation du GC est l'éligibilité des objets à la récupération de place.

Maintenant, nous allons jeter un œil à la méthode System.gc () elle-même.

3. System.gc ()

Une invocation de la méthode est simple:

System.gc()

La documentation officielle d'Oracle indique que:

L'appel de la méthode gc suggère que la machine virtuelle Java déploie des efforts pour recycler les objets inutilisés afin de rendre la mémoire qu'ils occupent actuellement disponible pour une réutilisation rapide.

Il n'y a aucune garantie que le GC réel sera déclenché .

System.gc () déclenche un GC majeur. Par conséquent, il y a un risque de passer du temps sur la phase d'arrêt du monde, en fonction de l'implémentation de votre garbage collector. En conséquence, nous avons un outil peu fiable avec une pénalité de performance potentiellement importante .

L'existence d'un appel explicite de ramasse-miettes devrait être un signal d'alarme sérieux pour tout le monde.

Nous pouvons empêcher System.gc () d'effectuer tout travail en utilisant l' indicateur JVM -XX: DisableExplicitGC .

3.1. L'optimisation des performances

Il convient de noter que juste avant de lancer une OutOfMemoryError, la JVM effectuera un GC complet. Par conséquent, un appel explicite à System.gc () ne nous sauvera pas de l'échec .

Les éboueurs de nos jours sont vraiment intelligents. Ils ont toutes les connaissances sur l'utilisation de la mémoire et d'autres statistiques pour être en mesure de prendre les bonnes décisions. Par conséquent, nous devons leur faire confiance.

En cas de problèmes de mémoire, nous avons un tas de paramètres que nous pouvons modifier pour régler notre application - en commençant par le choix d'un autre garbage collector, en définissant le rapport temps d'application / temps GC souhaité, et enfin, en terminant par la définition de tailles fixes pour les segments de mémoire.

Il existe également des moyens d'atténuer les effets de Full GC causés par un appel explicite. Nous pouvons utiliser l'un des drapeaux:

-XX:+ExplicitGCInvokesConcurrent

ou:

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

Si nous voulons vraiment que notre application fonctionne correctement, nous devons résoudre le véritable problème de mémoire sous-jacent.

Dans le chapitre suivant, nous verrons un exemple pratique où appeler explicitement System.gc () semble être utile.

4. Exemple d'utilisation

4.1. Scénario

Écrivons une application de test. Nous voulons trouver une situation où l'appel de System.gc () pourrait être utile .

Le garbage collection mineur se produit plus souvent que le principal. Donc, nous devrions probablement nous concentrer sur ce dernier. Un seul objet est déplacé vers un espace titulaire s'il a «survécu» à quelques collections et est toujours accessible à partir des racines GC.

Imaginons que nous ayons une énorme collection d'objets vivants depuis un certain temps. Ensuite, à un moment donné, nous effaçons la collection d'objets. C'est peut-être un bon moment pour exécuter System.gc () ?

4.2. Application de démonstration

Nous allons créer une application console simple qui nous permettra de simuler ce scénario:

public class DemoApplication { private static final Map cache = new HashMap(); public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { final String next = scanner.next(); if ("fill".equals(next)) { for (int i = 0; i < 1000000; i++) { cache.put(randomUUID().toString(), randomUUID().toString()); } } else if ("invalidate".equals(next)) { cache.clear(); } else if ("gc".equals(next)) { System.gc(); } else if ("exit".equals(next)) { System.exit(0); } else { System.out.println("unknown"); } } } }

4.3. Lancer la démo

Lançons notre application avec quelques indicateurs supplémentaires:

-XX:+PrintGCDetails -Xloggc:gclog.log -Xms100M -Xmx500M -XX:+UseConcMarkSweepGC

Les deux premiers indicateurs sont nécessaires pour consigner les informations du GC. Les deux indicateurs suivants définissent la taille initiale du tas, puis la taille maximale du tas. Nous voulons garder la taille du tas faible pour forcer GC à être plus actif. Enfin, nous décidons d'utiliser CMS - Concurrent Mark and Sweep garbage collector. Il est temps d'exécuter notre application!

Tout d'abord, essayons de remplir l'espace permanent . Tapez fill.

Nous pouvons étudier notre fichier gclog.log pour voir ce qui s'est passé. Nous verrons environ 15 collections. La ligne enregistrée pour les collections uniques ressemble à ceci:

197.057: [GC (Allocation Failure) 197.057: [ParNew: 67498K->40K(75840K), 0.0016945 secs] 168754K->101295K(244192K), 0.0017865 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] secs]

Comme on peut le voir, la mémoire est remplie.

Ensuite, let force System.gc () en tapant gc . Nous pouvons voir que l'utilisation de la mémoire n'a pas changé de manière significative:

238.810: [Full GC (System.gc()) 238.810: [CMS: 101255K->101231K(168352K); 0.2634318 secs] 120693K->101231K(244192K), [Metaspace: 32186K->32186K(1079296K)], 0.2635908 secs] [Times: user=0.27 sys=0.00, real=0.26 secs]

After a few more runs, we'll see that memory size stays at the same level.

Let's clear the cache by typing invalidate. We should see no more log lines appear in the gclog.log file.

We can try to fill cache a few more times, but no GC is happening. This is a moment when we can outsmart the garbage collector. Now, after forcing GC, we'll see a line like:

262.124: [Full GC (System.gc()) 262.124: [CMS: 101523K->14122K(169324K); 0.0975656 secs] 103369K->14122K(245612K), [Metaspace: 32203K->32203K(1079296K)], 0.0977279 secs] [Times: user=0.10 sys=0.00, real=0.10 secs]

We've released an impressive amount of memory! But was it really necessary right now? What happened?

According to this example, calling System.gc() might seem tempting when we're releasing big objects or invalidating caches.

5. Other Usages

There are very few reasons when an explicit call to the System.gc() method might be useful.

One possible reason is cleaning memory after server startup — we're starting a server or application which does a lot of preparation. After that, there are a lot of objects to be finalized. However, cleaning after such preparation shouldn't be our responsibility.

Another is memory leak analysisit's more a debugging practice than something we would like to keep in the production code. Calling System.gc() and seeing heap space still being high might be an indication of a memory leak.

6. Summary

In this article, we investigated the System.gc() method and when it might seem useful.

Nous ne devons jamais nous y fier lorsqu'il s'agit de l'exactitude de notre application. GC dans la plupart des cas est plus intelligent que nous, et en cas de problème de mémoire, nous devrions envisager de régler la machine virtuelle au lieu de faire un tel appel explicite.

Comme d'habitude, le code utilisé dans cet article se trouve à l'adresse over sur GitHub.