Les types de références dans la JVM
La machine virtuelle Java (JVM) gère quatre catégories de références : les références fortes, les références souples, les références faibles et les références fantômes. Ce texte se concentre sur les références fortes et souples.
Référence forte
Une référence forte est créée lors de l'instanciation d'un objet avec le mot-clé new. Tant qu'une telle référence existe, le ramasse-miettes (GC) ne récupère pas l'objet, même en cas de manque de mémoire. Ces références sont une cause fréquente de fuites mémoire car elles empêchent la libération des objets.
Référence souple
La référence souple (SoftReference) est un mécanisme permettant au ramasse-miettes de récupérer l'objet référencé uniquement lorsque la mémoire est insuffisante. Elle est idéale pour les caches sensibles à la mémoire, comme les images, où les données peuvent être libérées automatiquement pour éviter les erreurs OutOfMemoryError.
Pour créer une référence souple, on utilise le constructeur new SoftReference<>(Object referent), où referent est l'objet à référencer.
// Création d'un objet à lier par une référence souple
Object monObjet = new Object();
// Initialisation de la SoftReference pointant vers cet objet
SoftReference<Object> refSouple = new SoftReference<>(monObjet);
File d'attente de référence et récupération
La classe ReferenceQueue dans le paquet java.lang.ref sert à recevoir les références des objets marqués pour suppression par le ramasse-miettes. Lorsqu'un objet est sur le point d'être récupéré, toute référence souple, faible ou fantôme qui lui est associée est placée dans cette file. Ce mécanisme permet de détecter les références libérées et d'effectuer des actions complémentaires, améliorant ainsi la flexibilité du code.
Exemple de cache
L'implémentation suivante illustre un cache pour des objets Personne en utilisant des références souples et une ReferenceQueue. La classe principale CachePersonne gère le stockage et la récupération des données.
Méthode ajouterAuCache
Cette méthode ajoute une personne au cache via une référence souple.
- Elle commence par appeler
nettoyerLeCachepour éliminer les références obsolètes. - Elle crée une instacne de
RefPersonnequi encapsule l'objetPersonneet la file d'attente. - Elle stocke cette référence dans une table de hachage avec l'identifiant de la personne comme clé.
- Elle affiche la taille actuelle du cache.
public void ajouterAuCache(Personne p) {
nettoyerLeCache();
RefPersonne ref = new RefPersonne(p, fileAttente);
references.put(p.getNumeroId(), ref);
System.out.println(references.size());
}
Méthode recupererPersonne
Cette méthode récupère une personne à partir du cache.
- Elle vérifie si la clé (identifiant) est présente dans la map.
- Si oui, elle récupère la
RefPersonnecorrespondante et tente d'obtenir l'objet viaget(). - Si non, elle retourne
null.
public Personne recupererPersonne(Long id) {
Personne resultat = null;
if (references.containsKey(id)) {
RefPersonne ref = references.get(id);
resultat = ref.get();
}
return resultat;
}
Méthode nettoyerLeCache
Cette méthode supprime les références dont les objets ont été récupérés par le ramasse-miettes.
- Elle itère sur la file d'attente pour extriare les
RefPersonnepolluées. - Pour chaque référence non nulle, elle la retire de la table de hachage en utilisant sa clé interne.
private void nettoyerLeCache() {
RefPersonne ref;
while ((ref = (RefPersonne) fileAttente.poll()) != null) {
references.remove(ref.cleInterne);
}
}
Configuration et résultats
Pour observer le comportement du ramasse-miettes, configurez la JVM avec une taille de tas maximale de 10 Mo à l'aide de l'option -Xmx10m.
Lors de l'exécution, aucune exception OutOfMemoryError ne survient, et la taille de la table de hachage oscille dans une plage constante, prouvant que le ramasse-miettes libère efficacement les objets en mémoire insuffisante, évitant ainsi les fuites mémoire.
Code complet
Personne.java
package fr.exemple.modele;
public class Personne {
private Long numeroId;
private String nom;
public Personne(Long numeroId, String nom) {
this.numeroId = numeroId;
this.nom = nom;
}
public Personne() {
}
public Long getNumeroId() {
return numeroId;
}
public void setNumeroId(Long numeroId) {
this.numeroId = numeroId;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
}
CachePersonne.java
package fr.exemple.cache;
import fr.exemple.modele.Personne;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
public class CachePersonne {
private class RefPersonne extends SoftReference<Personne> {
private Long cleInterne;
public RefPersonne(Personne p, ReferenceQueue<? super Personne> file) {
super(p, file);
cleInterne = p.getNumeroId();
}
}
private static CachePersonne instanceUnique = new CachePersonne();
private Map<Long, RefPersonne> references;
private ReferenceQueue<Personne> fileAttente;
private CachePersonne() {
references = new HashMap<>();
fileAttente = new ReferenceQueue<>();
}
public static CachePersonne getInstance() {
return instanceUnique;
}
public void ajouterAuCache(Personne p) {
nettoyerLeCache();
RefPersonne ref = new RefPersonne(p, fileAttente);
references.put(p.getNumeroId(), ref);
System.out.println(references.size());
}
public Personne recupererPersonne(Long id) {
Personne resultat = null;
if (references.containsKey(id)) {
RefPersonne ref = references.get(id);
resultat = ref.get();
}
return resultat;
}
private void nettoyerLeCache() {
RefPersonne ref;
while ((ref = (RefPersonne) fileAttente.poll()) != null) {
references.remove(ref.cleInterne);
}
}
}
TestCache.java
package fr.exemple;
import fr.exemple.cache.CachePersonne;
import fr.exemple.modele.Personne;
public class TestCache {
public static void main(String[] args) {
for (Long i = 0L; ; i++) {
CachePersonne.getInstance().ajouterAuCache(new Personne(i, String.valueOf(i)));
}
}
}