garbage-collectionPremiers pas avec la collecte des ordures


Remarques

Le nettoyage de la mémoire (GC) est un moyen de récupérer automatiquement la mémoire occupée par des objets dont un programme n'a plus besoin. Ceci contraste avec la gestion manuelle de la mémoire où le programmeur spécifie explicitement quels objets doivent être désalloués et renvoyés en mémoire. De bonnes stratégies de GC peuvent être plus efficaces que la gestion manuelle de la mémoire, mais cela peut dépendre du type de logiciel.

Les principaux avantages de la collecte des ordures sont les suivants:

  • Cela libère le programmeur de la gestion manuelle de la mémoire.
  • Cela évite certains bogues difficiles à trouver lors de la gestion manuelle de la mémoire (p. Ex. Pointeurs, double libération, certains types de fuites de mémoire).
  • Les langages qui utilisent la récupération de place sont généralement moins complexes.

Les principaux inconvénients sont les suivants:

  • La récupération de la mémoire a quelques inconvénients par rapport à la gestion manuelle de la mémoire.
  • Cela peut avoir un impact sur les performances, en particulier lorsque la récupération de mémoire est déclenchée à des moments indésirables.
  • C'est indéterminé, le programmeur ne sait pas quand le ramassage des ordures est fait et si les objets sont libérés ou non.

La plupart des langages de programmation les plus récents intègrent la récupération de mémoire, par exemple Java, C #, .NET, Ruby et JavaScript. Les anciens langages tels que C et C ++ ne disposent pas de la récupération de mémoire, bien qu'il existe des implémentations disponibles avec la récupération de place. Il existe également des langages vous permettant d'utiliser une combinaison de récupération de place et de gestion manuelle de la mémoire, par exemple Modula-3 et Ada.

Les stratégies de collecte des ordures diffèrent, mais beaucoup utilisent une approche de variation du mark-and-sweep. Dans la phase de marquage, tous les objets accessibles sont trouvés et marqués. Lors de la phase de balayage, le tas est analysé pour rechercher des objets inaccessibles et non marqués qui sont ensuite nettoyés. Les collecteurs de mémoire modernes utilisent également une approche générationnelle dans laquelle deux ou plusieurs régions d'allocation d'objets (générations) sont conservées. La génération la plus jeune contient les objets les plus récents et est nettoyée plus souvent. Les objets qui «survivent» pendant un certain temps sont promus à une génération plus ancienne.

De nombreux langages avec GC permettent aux programmeurs de l'ajuster (voir par exemple le Guide d'optimisation de la collecte des ordures de la machine virtuelle Java 8 ou la documentation de la collecte des ordures .Net ).

Activation de la journalisation gc verbeuse en Java

Normalement, le nettoyage de la mémoire de jvm (gc) est transparent pour l'utilisateur (développeur / ingénieur).

Le réglage du GC n'est normalement pas requis, sauf si l'utilisateur fait face à une fuite de mémoire ou à une application nécessitant une grande quantité de mémoire, entraînant éventuellement une exception de mémoire insuffisante qui oblige l'utilisateur à examiner le problème.

La première étape consiste généralement à augmenter la mémoire (soit le tas, soit le perm-gen / meta-space, selon que le chargement à l'exécution est important ou que la base de la bibliothèque est volumineuse ou qu'il y a une fuite dans le chargement de classe ou le thread). mécanisme de manipulation). Mais chaque fois que ce n’est pas possible, l’étape suivante consiste à essayer de comprendre ce qui ne va pas.

Si l'on veut juste l'instantané à un instant donné, l'utilitaire jstat faisant partie du jdk suffirait.

Cependant, pour une compréhension plus détaillée, il est utile d'avoir un journal contenant l'instantané du tas avant et après chaque événement gc. Pour cela, l'utilisateur doit activer la journalisation gc verbeuse en utilisant -verbose:gc dans le cadre des paramètres de démarrage de jvm et en incluant -XX:+PrintGCDetails et -XX:+PrintGCTimeStamp .

Pour ceux qui voudraient profiler leur application de manière proactive, il existe également des outils tels que jvisualvm qui fait également partie du jdk, grâce auquel ils peuvent mieux comprendre le comportement des applications.

Voici un exemple de programme, la configuration gc et la sortie du journal verbose-gc:

package com.example.so.docs.gc.logging;

import java.util.Arrays;
import java.util.Random;

public class HelloWorld {

    public static void main(String[] args) {
        sortTest();
    }
    
    private static void sortTest() {
        System.out.println("HelloWorld");
        
        int count = 3;
        while(count-- > 0) {
            int size = 1024*1024;
            int[] numbers = new int[size];
            Random random = new Random();
            for(int i=0;i<size;i++) {
                numbers[i] = random.nextInt(size);
            }
            
            Arrays.sort(numbers);
        }
        System.out.println("Done");
        
    }
    

}
 

Options du GC:

-server -verbose:gc  -XX:+PrintGCDetails -XX:+PrintGCTimeStamps  -Xmx10m  -XX:-PrintTenuringDistribution  -XX:MaxGCPauseMillis=250 -Xloggc:/path/to/logs/verbose_gc.log
 

Sortie:

Java HotSpot(TM) 64-Bit Server VM (25.72-b15) for windows-amd64 JRE (1.8.0_72-b15), built on Dec 22 2015 19:16:16 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 6084464k(2584100k free), swap 8130628k(3993460k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxGCPauseMillis=250 -XX:MaxHeapSize=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:-PrintTenuringDistribution -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.398: [GC (Allocation Failure) [PSYoungGen: 483K->432K(2560K)] 4579K->4536K(9728K), 0.0012569 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.400: [GC (Allocation Failure) [PSYoungGen: 432K->336K(2560K)] 4536K->4440K(9728K), 0.0008121 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.401: [Full GC (Allocation Failure) [PSYoungGen: 336K->0K(2560K)] [ParOldGen: 4104K->294K(5632K)] 4440K->294K(8192K), [Metaspace: 2616K->2616K(1056768K)], 0.0056202 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
0.555: [GC (Allocation Failure) [PSYoungGen: 41K->0K(2560K)] 4431K->4390K(9728K), 0.0004678 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.555: [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 4390K->4390K(9728K), 0.0003490 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.556: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 4390K->293K(5632K)] 4390K->293K(8192K), [Metaspace: 2619K->2619K(1056768K)], 0.0060187 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 2560K, used 82K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd14938,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 5632K, used 4389K [0x00000000ff600000, 0x00000000ffb80000, 0x00000000ffd00000)
  object space 5632K, 77% used [0x00000000ff600000,0x00000000ffa49670,0x00000000ffb80000)
 Metaspace       used 2625K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 282K, capacity 386K, committed 512K, reserved 1048576K
 

Voici quelques liens utiles sur GC:

  1. Une page archivée expliquant les concepts gc (jdk7)
  2. Tutoriel du collecteur G1
  3. Options de VM utiles
  4. JDK 5 - GC Ergonomics (les concepts sont toujours pertinents)
  5. JDK 6 Tuning (les concepts sont toujours pertinents)

introduction

Les objets deviennent éligibles à la récupération de place s'ils ne sont plus accessibles par le ou les principaux points d'entrée d'un programme. Le GC n'est généralement pas exécuté explicitement par l'utilisateur, mais pour que le GC sache qu'un objet n'est plus nécessaire, un développeur peut:

Dereference / assign null

someFunction {
     var a = 1;
     var b = 2;
     a = null; // GC can now free the memory used for variable a
     ...
} // local variable b not dereferenced but will be subject to GC when function ends
 

Utilisez des références faibles

La plupart des langages avec GC vous permettent de créer des références faibles à un objet qui ne comptent pas comme référence pour le GC. S'il n'y a que des références faibles à un objet et aucune référence forte (normale), alors l'objet est éligible pour GC.

WeakReference wr = new WeakReference(createSomeObject());
 

Notez qu'après ce code, il est dangereux d'utiliser la cible de la référence faible sans vérifier si l'objet existe toujours. Les programmeurs débutants commettent parfois l'erreur d'utiliser du code comme celui-ci:

if wr.target is not null {
    doSomeAction(wr.target);
}
 

Cela peut entraîner des problèmes car GC peut avoir été appelé après la vérification NULL et avant l'exécution de doSomeAction. Il est préférable de créer d'abord une référence forte (temporaire) à l'objet comme ceci:

Object strongRef = wr.target;
if strongRef is not null {
    doSomeAction(strongRef);
}
strongRef = null;