Bonjour à tous,

Commençons par enfoncer les portes ouvertes : la JVM et le par extention le garbage collector, s'occupant pour nous de la gestion de la mémoire, on ne devrait pas avoir besoin de gréer de "pointeurs".
Mais faute est de constater que je suis souvent rencontré au même problème, dès lors qu'il s'agit de partager des objets de "ressources" entre d'autres objets, de manière intelligente. C'est à dire qu'il s'agit plus de maîtriser l'ouverture et la fermeture de ressource partagée, que de gérer la mémoire à proprement parler.

Je vais prendre pour exemple, le typique cas du fichier ouvert. Imaginons que plusieurs objets aient besoin d'accéder au contenu d'un fichier, mais par un unique FileInputStream (merci de passer outre la stupidité de la chose ).

Nous avons donc un objet A qui va ouvrir un fichier. Puis un objet B va venir récupérer le même flux à son tour. Si A est détruit, on ne souhaite pas fermer le fichier, car B l'utilise toujours. De même, si B n'est plus utilisé, il faut s'assurer qu'aucun autre objet ne l'utilise avant de fermer finalement le fichier.

La difficulté est donc de maintenir un compteur, pour savoir combien de fois l'objet est utiliser.
Jusqu'à présent, j'utilisait une classe de "Manager", auprès duquel mes objets venaient récupérer la ressource, et incrémenter le compteur. De même, quand il n'en ont plus besoin, ils appellent la méthode release() du manager, qui décrémente le compteur. Arrivé à 0, celui-ci ferme la ressource.

Seulement voilà, créer un Manager pour chaque ressource est fastidieux, et amène de la maintenance inutile, et crée des dépendances entre plus de classes que nécessaire.

Un point aussi sur les objets WeakReference. Ceux ci ne me semblent pas tellement adapté à ce genre de situation. En effetn, le but ici n'est pas de permettre ou non la suppression d'un objet utilisé par un autre, mais de récupérer un événement au moment où l'objet n'est plus utilisé.

J'ai donc imaginé une classe "SharedReference" gérant cette problématique. Celle-ci étant finalement très simple, je n'ai pas pris la peine de la commenterdans le code qui va suivre. Cette classe simule l'encapsulation d'une référence. En réalité, elle contient une classe interne (qui contiendra la référence de la ressource et le compteur), dont l'instance sera partagée avec tous les SharedReference voulant utiliser la même ressource.

L'objectif final est de gérer de manière plus simple le cycle de vie des objets, et de limiter les risques de "fuites mémoire" dues à des objets restant indéfiniment dans des collections, car on a oublié d'appeler la méthode pour retirer l'objet. Ce qui arrive plus fréquement qu'on ne le croit.

Pour son fonctionnement, la classe prend un callback, qui sera appelé lors de la "destruction" de la ressource (au moment où le compteur arrive à 0). Pour être véritablement réactif, il reste important d'appeler directement la méthode release() de SharedReference quand on n'a plus besoin de al ressource. Toutefois, le garbage collector peut s'en charger par la méthode finalize() en cas d'oubli !

Je vous demanderais également de ne pas considérer les problèmes de tread-safety ici, ni même le fait que le "destructeur" (du code "client") soit appelé par le thread du garbadge collector. Il ne s'agit pas de la véritable implémentation que je souhaite utiliser, mais plus du principe global.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class SharedReference<T> {
 
    private static final class HardReference<T> {
 
	private T ref;
 
	private int count = 0;
 
	private final Consumer<T> destructor;
 
	public HardReference(T ref, final Consumer<T> destructor) {
	    this.ref = ref;
	    this.destructor = destructor;
	}
 
	public void acquire() {
	    ++count;
	}
 
	public void release() {
	    count--;
	    if (count == 0) {		
		destructor.accept(ref);
		ref = null;
	    }
	}
 
	public T get() {
	    if( count > 0) {
		return ref;
	    }
	    return null;
	}
 
    }
 
    private final HardReference<T> ref;
 
    private boolean alive = true;
 
    public SharedReference(T val, final Consumer<T> destructor) {
	ref = new HardReference<>(val, destructor);
	ref.acquire();
    }
 
    public SharedReference(SharedReference<T> sharedRef) {
	ref = sharedRef.ref;
	ref.acquire();
    }
 
    protected void finalize() throws Throwable {
	try {
	    this.release();
	} finally {
	    super.finalize();
	}
    }
 
    public void release() {
	if (alive) {
	    alive = false;
	    ref.release();
	}
    }
 
    public int count() {
	return this.ref.count;
    }
 
    public T get() {
	if (alive) {
	    return this.ref.get();
	}
	return null;
    }
 
}
Exemple d'utilisation :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public static void main(String args[]) {
	test1();
	test2();
    }
 
    public static void test1() {
 
	System.out.println("--- test1 ---");
 
	SharedReference<String> ref1 = new SharedReference<>("test", (r) -> {
	    System.out.println("destroy " + r);
	});
 
	System.out.println(ref1.count()); 	// affiche 1
	System.out.println(ref1.get());   	// affiche "test"
 
	SharedReference<String> ref2 = new SharedReference<>(ref1);
 
	System.out.println(ref2.count()); 	// affiche 2
	System.out.println(ref2.get());   	// affiche "test"
 
	ref1.release();
 
	System.out.println(ref2.count()); 	// affiche 1
 
	ref2.release(); 			// affiche "destroy test"
 
	System.out.println(ref2.count()); 	// affiche 0
	System.out.println(ref2.get());   	// affiche "null"
 
    }
 
    public static void test2() {
 
	System.out.println("--- test2 ---");
 
	SharedReference<String> ref = test2inner();
 
	System.out.println(ref.count());	// affiche toujours 2
 
	System.gc(); 				// le garbage collector finit par passer
 
	try {
	    Thread.sleep(100L);
	} catch (InterruptedException e) {
	}
 
	System.out.println(ref.count());	// affiche 1
	System.out.println(ref.get()); 		// affiche "test"
 
	ref.release();				// affiche "destroy test" 
 
    }
 
    public static SharedReference<String> test2inner() {
 
	SharedReference<String> ref = new SharedReference<>("test", (r) -> {
	    System.out.println("destroy " + r);
	});
 
	// Oubli volontaire d'appeler ref.release() !
	return new SharedReference<>(ref);
    }
Vos avis ? Qu'en pensez vous ?

Je posterais la version réelle, thread-safe, quand je l'aurais terminée si cela vous intéresse.
On peut également imaginer d'autres outils utilisant cette classe; comme par exemple des collections se vidant automatiquement.