Premiers pas avec la récupération de place

Introduction

Les objets deviennent éligibles pour la récupération de place (GC) s’ils ne sont plus accessibles par le(s) point(s) d’entrée principal(s) d’un programme. Le GC n’est généralement pas exécuté explicitement par l’utilisateur, mais pour faire savoir au GC qu’un objet n’est plus nécessaire, un développeur peut :

Déréférencer / attribuer 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 font parfois l’erreur d’utiliser un code comme celui-ci :

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

Cela peut causer 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; 

Activation de la journalisation gc détaillée en Java

Normalement, la récupération de place de la jvm (gc) est transparente pour l’utilisateur (développeur/ingénieur).

Le réglage GC n’est normalement pas nécessaire, sauf si l’utilisateur est confronté à une fuite de mémoire ou a une application qui nécessite une grande quantité de mémoire - les deux conduisant finalement à 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 cela soit dû à la charge au moment de l’exécution ou que la base de bibliothèque de l’application soit grande ou qu’il y ait une fuite dans le classloading ou le thread -mécanisme de manutention). Mais chaque fois que cela 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é, alors l’utilitaire “jstat” qui fait 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 détaillée en utilisant -verbose:gc dans le cadre des paramètres de démarrage de jvm et en incluant les drapeaux -XX:+PrintGCDetails et -XX:+PrintGCTimeStamp.

Pour ceux qui souhaitent 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 avoir un aperçu du comportement des applications.

Vous trouverez ci-dessous 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 CG :

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

Production :

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

Ci-dessous quelques liens utiles sur GC :

  1. [Une page archivée expliquant les concepts gc (jdk7)][1]
  2. [Tutoriel du collecteur G1][2]
  3. [Options de machine virtuelle utiles] [3]
  4. [JDK 5 - GC Ergonomie (les concepts sont toujours d’actualité)][4]
  5. [JDK 6 Tuning (les concepts sont toujours pertinents)][5]

[1] : https://web.archive.org/web/20160812164610/http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html [2] : http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html [3] : http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html [4] : http://docs.oracle.com/javase/1.5.0/docs/guide/vm/gc-ergonomics.html [5] : http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html