Localisation des objets dans la JVM
En Java, une variable de référence sur la pile manipule l'objet concret stocké dans le tas (heap). La méthode d'accès à l'objet dépend de l'implémentation de la machine virtuelle. Deux approches dominantes existent : l'utilisation de handles (poignées) et le pointage direct.
- Avec des handles : Le tas est divisé en une zone de pool de handles. La variable de référence contient l'adresse du handle, lequel contient lui-même les adresses des données d'instance et des données de type de l'objet. L'avantage principal est la stabilité de l'adresse stockée dans la référence. Si l'objet est déplacé, seul le pointeur d'instance dans le handle est modifié, la référence reste inchangée.
- Avec un pointeur direct : La variable de référence contient directement l'adresse de l'objet. L'objet lui-même contient un pointeur vers ses données de type. L'avantage majeur de cette approche est la vitesse d'accès, car elle économise une étape de déréférencement. La machine virtuelle HotSpot utilise principalement des pointeurs directs.
Les quatre types de références
Caractéristiques :
- Référence forte : L'objet n'est jamais collecté par le GC tant qu'une référence forte pointe vers lui.
- Référence souple : L'objet est collecté uniquement lorsque la mémoire devient insuffisante.
- Référence faible : L'objet est systématiquement collecté à chaque cycle de GC.
- Référence fantôme : L'objet ne peut jamais être accédé via cette référence ; elle sert principalement à la gestion de la mémoire native.
1. Référence forte
C'est le type de référence le plus courant.
// Un exemple typique de référence forte
Object instance = new Object();
Tant qu'un objet possède une référence forte, le ramasse-miettes (GC) ne le colletcera pas. Même en cas de mémoire insuffisante, une exception OutOfMemoryError sera lancée plutôt que de le récupérer. C'est pourquoi les références fortes sont une cause majeure de fuites mémoire en Java. Un objet sans autre référence forte devient éligible à la collecte lorsque sa portée de référence est dépassée ou quand sa référence forte est explicitement définie à null. Le moment exact de la collecte dépend de la stratégie du GC.
public class ObjetDemo {
@Override
protected void finalize() {
System.out.println("ObjetDemo a été finalisé.");
}
}
public class DemoReferenceForte {
public static void main(String[] args) {
ObjetDemo demo = new ObjetDemo();
System.out.println("Référence initiale : " + demo);
System.gc();
System.out.println("Après GC (référence forte) : " + demo);
demo = null;
System.gc();
System.out.println("Après null et GC : " + demo);
}
}
Sortie possible :
Référence initiale : ObjetDemo@1b6d3586
Après GC (référence forte) : ObjetDemo@1b6d3586
Après null et GC : null
ObjetDemo a été finalisé.
2. Référence souple (Soft Reference)
Implémentée via la classe java.lang.ref.SoftReference.
Sa durée de vie est plus courte que celle d'une référence forte. Le GC ne tentera de collecter l'objet référencé que lorsqu'il estimera que la mémoire est insuffisante. La JVM s'assure de libérer ces objets avant de lancer une OutOfMemoryError. Une SoftReference peut être associée à une ReferenceQueue. Si l'objet est collecté, la référence est placée dans cette file.
SoftReference<objetdemo> refSouple = new SoftReference<>(new ObjetDemo());
</objetdemo>
L'objet survit aux collections GC normales tant que la mémoire suffit. Il est collecté pour libérer de l'espace uniquement en cas de pression mémoire. Ce mécanisme est couramment utilisé pour implémenter des caches mémoire.
import java.lang.ref.SoftReference;
public class DemoReferenceSouple {
public static void main(String[] args) {
// Créer une référence souple vers un objet simple
SoftReference<objetdemo> refObjet = new SoftReference<>(new ObjetDemo());
// Créer une référence souple vers un gros tableau
SoftReference<byte> refTableau = new SoftReference<>(new byte[1024 * 1024 * 20]); // 20 Mo
System.out.println("Objet via ref souple : " + refObjet.get());
System.out.println("Tableau via ref souple : " + refTableau.get());
System.gc();
System.out.println("Après GC, objet : " + refObjet.get()); // Devrait exister
// Allouer un autre gros tableau pour forcer la pression mémoire
// (Supposons un heap limité via -Xmx35m pour cet exemple)
byte[] grosTableau = new byte[1024 * 1024 * 20];
System.out.println("Après allocation supplémentaire et GC, objet : " + refObjet.get()); // Peut être null
System.out.println("Après allocation supplémentaire et GC, tableau : " + refTableau.get()); // Peut être null
}
}
</byte></objetdemo>
3. Référence faible (Weak Reference)
Implémentée via la classe java.lang.ref.WeakReference.
Sa durée de vie est plus courte que celle d'une référence souple. Dès que le GC trouve un objet possédant uniquement une référence faible, il le collecte, quel que soit l'état de la mémoire. Comme le GC tourne en arrière-plan avec une basse priorité, la collecte n'est pas immédiate. Comme pour les références souples, une association avec une ReferenceQueue est possible.
WeakReference<objetdemo> refFaible = new WeakReference<>(new ObjetDemo());
</objetdemo>
L'objet est collecté au prochain cycle de GC si aucune autre référence forte ou souple ne pointe vers lui. Un cas d'utilisation classique est la prévention des fuites mémoire dans les ThreadLocal.
import java.lang.ref.WeakReference;
public class DemoReferenceFaible {
public static void main(String[] args) {
ObjetDemo instance = new ObjetDemo();
WeakReference<objetdemo> refFaible = new WeakReference<>(instance);
System.out.println("Objet via ref faible : " + refFaible.get());
instance = null; // Supprimer la référence forte directe
System.gc();
System.out.println("Après GC, objet via ref faible : " + refFaible.get()); // Sera null
}
}
</objetdemo>
4. Référence fantôme (Phantom Reference)
Implémentée via la classe java.lang.ref.PhantomReference.
Il est impossible d'accéder à l'objet via une référence fantôme (get() retourne toujours null). Son rôle est de fournir un mécanisme de notification post-finalisation. Un objet ne possédant qu'une référence fantôme peut être collecté à tout moment. L'utilisation d'une ReferenceQueue est obligatoire. Lorsque le GC s'apprête à collecter un objet, il place sa référence fantôme dans la file associée. Le programme peut alors interroger cette file pour prendre des mesures avant que la mémoire ne soit libérée.
ReferenceQueue<objetdemo> fileAttente = new ReferenceQueue<>();
PhantomReference<objetdemo> refFantome = new PhantomReference<>(new ObjetDemo(), fileAttente);
</objetdemo></objetdemo>
Son usage principal concerne la gestion de la mémoire native (hors tas JVM). Le GC ne gère que la mémoire interne de la JVM. Pour des données en mémoire système, la JVM maintient une référence (pointeur) vers cette mémoire. Lors de la collecte de cette référence, la référence fantôme est utilisée pour déclencher la libération de la mémoire native associée, via la file de notification.
import java.lang.ref.*;
public class DemoReferenceFantome {
private static final ReferenceQueue<objetdemo> FILE = new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<objetdemo> refFantome = new PhantomReference<>(new ObjetDemo(), FILE);
System.out.println("Tentative d'accès via ref fantôme : " + refFantome.get()); // Toujours null
// Simuler une boucle de surveillance de la file
while (true) {
System.gc();
Reference extends ObjetDemo> refRecuperee = FILE.poll();
if (refRecuperee != null) {
System.out.println("Référence fantôme détectée dans la file. Objet éligible à la finalisation/libération.");
break;
}
// Attendre un peu pour ne pas saturer le CPU
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
}
</objetdemo></objetdemo>
5. La référence finalizer
La classe Object définit une méthode finalize(). Lorsqu'un objet n'est plus référencé, une référence de finalisation est placée dans une file. Le système localise ensuite l'objet via cette référence et appelle sa méthode finalize(). Une fois cette méthode exécutée, l'objet devient éligible à la collecte.
La file de références (ReferenceQueue)
- Les références souples et faibles peuvent être associées à une file. Après la collecte de l'objet, elles y sont placées pour permettre un nettoyage.
- Les références fantômes et de finalisation doivent être associées à une file.
Scénarios d'applicasion
- Référence forte : Usage courant pour maintenir un objet en vie.
- Référence souple : Idéale pour les caches mémoire, où les données ne sont pas critiques.
- Référence faible : Utile pour éviter les fuites mémoire, notamment dans
ThreadLocalou dans les contextes où les objets sont liés à des cycles de vie courts. - Référence fantôme : Employée pour la gestion proactive des ressources natives et le nettoyage de la mémoire hors heap.